Context
Constraints
Decisions
Implementation
Outcome
Outcome metrics
- Maintainability
- Single home for shared visual + accessibility primitives
- Developer experience
- Storybook as contract surface for contributors across countries
- Accessibility
- Axe assertions on every component story in CI
- Scalability
- Used across Angular, AEM and partner SaaS UIs
Tradeoffs
We chose LitElement over a React component library exported via Web Components. The Web Component output from React libraries is fine in theory but tends to ship more runtime than Lit and to lose ergonomic features when wrapped. Lit’s “Web Components first” model meant the components behave the same in every host — no surprises in AEM, no hydration mismatches in partner stacks.
We chose Storybook as the contribution contract, not just as documentation. Every PR ships the story alongside the code; reviewers read the story to understand the change. The cost is that a component without a story doesn’t ship — and that’s the point.
We deliberately did not version the library as a monorepo of one-package-per-component. The maintenance overhead of dozens of packages didn’t pay for itself at our scale; a single library with semver and selective imports gave us 90% of the benefit with 10% of the operational cost.
Engineering challenges
- Theming across brands — multiple regional brands and product-line variants meant the token system had to be deep but cheap to override
- Coexistence with AEM CSS — the legacy AEM templates have their own selectors and specificity issues; the library had to be defensive about cascade
- Contributor onboarding across countries — async-first contribution model with documented patterns, because synchronous design reviews don’t scale across timezones
- API stability — once partners depend on a component, its API is a contract; breaking changes need migration paths
What I learned
A component library lives or dies by the contribution model. The code is easy compared to deciding who can add what, how, and on what schedule. We invested in the RFC-light process and in Storybook-as-contract long before we had the components to populate them, and it paid back every time a new team started contributing.
The other lesson: framework-agnostic isn’t an aesthetic choice, it’s a strategic one. The moment a SaaS partner could use the same button as our Angular app, the bank’s product surface got more consistent than any style guide could have made it.
What I would do differently
I’d ship the design tokens as a separate, versioned package from day one. We kept them inside the component library initially, which was fine until non-component consumers (AEM templates, partner CSS) needed the tokens without pulling the runtime. Splitting them later was straightforward but would have been free if we’d done it up front.
Future evolution
The next steps are SSR-friendly rendering for the components that show up in SEO-critical AEM pages, and a small set of AI-aware components (input fields with embedded assistant affordances, chat panels) for the AI features shipping into the platform. Beyond that, exploring whether the library is mature enough to open-source parts of it as a contribution back to the LitElement community.
Principles applied
- Framework-agnostic where it matters, framework-specific where it helps
- Accessibility as a primitive, not a layer
- Documentation as contract, not as afterthought
- Optimise the contribution model, not just the code
Related work
The accessibility primitives in this library are what made accessibility-by-default practical at scale — without them, every team would have re-implemented focus management. The library also ships components used by the AI features, so consistency carries through to the AI surface.