The thin-CMS pattern: deploying a design system into AEM
How we extended a design system from engineering into Adobe Experience Manager, one architectural rule that turned a developer tool into a business platform.
The gap
You build a design system. Engineers love it. Adoption climbs. Then you walk over to the marketing org and they’re hand-coding HTML in AEM templates because the design system was never wired up there.
This is the most common failure mode of mature design systems: the components serve engineers; the CMS serves the business; neither side connects. Marketing emails engineering for every new page. Engineering becomes a bottleneck for content that should be self-service. The brand experience drifts in the gap.
The fix isn’t more components. It’s making the components the CMS already understands.
The one architectural rule
Pick one principle and hold it under all pressure:
The design system owns components. The CMS consumes them. The CMS owns nothing component-shaped.
In our case the design system is AXS, the CMS is Adobe Experience Manager, and “consumes” means the components ship as standards-based custom elements that AEM templates declare directly.
<!-- An AEM template -->
<axs-card heading="${heading}" eyebrow="${eyebrow}">
<axs-rich-text>${body}</axs-rich-text>
</axs-card>
That’s the whole pattern. AEM dialogues collect authoring data. The template emits a custom element with attributes filled in. The browser instantiates the same component a Vue app would instantiate, same styles, same behavior, same accessibility tree.
What this rules out:
- AEM components that reimplement design-system styles.
- HTL fragments that override design-system markup.
- Brand updates that have to ship into AEM separately from the design system.
What it gives you, by construction:
- Brand updates flow to AEM the same release the design system ships them.
- A content author working in AEM gets the same components an engineer gets.
- The visual layer is single-sourced across applications, marketing, and content.
How the integration actually works
Three pieces:
1. axs-ui, the design system. Ships custom elements + tokens.
2. axs-aem-bundle, an AEM package that vendors axs-ui as a dependency.
Provides AEM components whose templates are just thin
wrappers around the custom element tags.
3. axs.css + axs.js, loaded into AEM-rendered pages via the same delivery
infrastructure as any other page asset.
The AEM component (in HTL) for axs-card is one line of structural markup. All it does is take the dialog properties and emit:
<axs-card heading="${heading}" eyebrow="${eyebrow}" ...>
<slot from-aem-richtext />
</axs-card>
The component’s behavior is in the custom element. The CMS is contributing structured attribute data and slot content, nothing else. That’s why we call it thin-CMS: AEM templates are the thinnest possible layer between the authoring UI and the custom element.
What this gives you
Faster content delivery. A marketer building a new product page picks from the design-system components, fills in attributes through the AEM dialog, and ships. No engineering ticket. No custom AEM components per page.
Cross-channel consistency by construction. The same axs-card renders identically in an Angular app, a Vue app, and an AEM-published marketing page. There’s no “marketing version” of the brand that drifts from the app version.
Brand updates that propagate. Update a token in the design system, ship a new version of axs-ui, redeploy. Every AEM page picks it up because the components are vendored, not reimplemented. The CMS doesn’t need to know anything changed.
Reduced AEM-specific engineering. AEM components become so thin that adding a new one is mostly an authoring concern, define the dialog, point it at an existing custom element. Engineering writes one wrapper per design-system component, not one wrapper per page or feature.
What this requires
The architectural rule is the easy part. The work that actually has to happen:
Authoring discipline. The AEM dialog has to map cleanly to component attributes. If a designer wants a card variant that doesn’t exist in the design system, you don’t fork the CMS template, you add the variant to the design system. That changes a workflow.
A clear ownership boundary. AXS owns the component. AEM owns the authoring experience and the slot content. Neither side touches the other’s surface. Decisions about styling go to AXS; decisions about authoring ergonomics go to AEM. Mixing those produces fragmentation in under a quarter.
Tokens have to pierce AEM. Custom properties on the page root, set by the AEM theme layer, consumed by the design-system components. If you can’t get tokens to flow into AEM-rendered pages, the pattern breaks.
Story alignment with marketing. This is the part most engineers underestimate. Marketing teams have to trust that the design system covers their needs before they’ll route requests through it instead of around it. That trust comes from demos, walkthroughs, and a real responsiveness to gaps. It is not a one-time launch.
Why it works
A design system is most valuable when the audience widest. The thin-CMS pattern extends the audience from engineers to anyone using the CMS, which is a much larger group at most companies. The same component primitives that supported multi-framework apps now serve marketing and content.
The pattern is also forward-compatible. The custom-element substrate is what made Gen UI work for us; the same substrate is what makes AEM thin. Both downstream wins came from the same upstream architectural decision.
The hardest part isn’t the engineering. It’s holding the ownership line consistently for years while individual exceptions pile up. Every exception is reasonable on its own. The cumulative cost of saying yes to all of them is the design system you used to have.