Replies: 4 comments
-
|
Some thoughts after reflecting on both this discussion and our meeting today: What we're already doing
We're closer to the vision than the document suggests. Data attribute exposure <Button variant="primary" size="default" tone="accent">Save</Button>
That said — agreed the internal names could be simpler (e.g. BEM "fights against the cascade" — does it though? Our BEM classes live inside
So the argument that BEM "fights the cascade" doesn't apply to how we're using it. Concrete example — which is easier to reason about in the inspector? /* BEM inside @layer — what we have */
.eds-switch__track { ... }
.eds-switch__handle { ... }
/* Single class + nesting — proposed */
.eds-switch > span:nth-child(2) { ... }
.eds-switch > span:nth-child(2) > span { ... }To add concrete context: there's an open issue from OPT (#4533) that illustrates exactly this need. They want a stable way to target the internal typography element in Button for style overrides — not because they want to fight the design system, but because .eds-button > p breaks silently on internal refactoring. Reducing data attributes — agreed If we can move typography and spacing decisions into the CSS itself — resolving them from a single Shared CSS between React and Web Components What actually has to match is the DOM structure. As long as the Web Component produces the same HTML as the React component, the same stylesheet works — whether it uses BEM classes or nesting selectors. Ironically, it's the data attributes that create friction for a web component migration — not BEM. Today we write: .eds-button[data-variant="primary"] { ... }In a web component, the attributes live on the host element ( Neither option is a dealbreaker, but it's worth noting: BEM classes are zero-effort to reuse across React and Web Components, while data attributes require either CSS changes or extra JS. If we're optimising for a smooth web component transition, BEM is actually the lower-risk approach. What I think we should actually do
|
Beta Was this translation helpful? Give feedback.
-
|
Lots of good points here, and I think we're more aligned than it might seem — Victor sets the direction, Just want to add one angle on the BEM discussion. BEM, nesting and CUBE CSSOn the BEM question specifically — I'm not strongly attached to it either way, but one thing struck me In native CSS nesting that doesn't work, so you end up writing: Which feels redundant — "eds-switch" appears twice. And just to clarify — I agree with Frida that What I mean is a middle ground: give those elements a simple class name and scope them to the parent block, Where native HTML is semantic enough — Frida's point about stable handles for consumers is valid though — Same readability and debuggability for consumers, but cleaner to write. Keep the root class Data attributes — variants vs internal stylingOn typography and spacing data attributes — in CUBE terms, In CUBE terms these would either be utility classes or just resolved directly in the component CSS, rather than Either way, reducing the number of attributes to coordinate would make components easier to reason about. CSS file organisation - keep it simple for nowOn CSS file organisation — I think we continue with one file per component imported into the global CSS, So that's my take — we could try the simpler sub-part class names on new components going forward, keep the bigger |
Beta Was this translation helpful? Give feedback.
-
|
Thanks both of you – this is exactly the kind of discussion I was hoping for, and between the two of you you've sharpened the proposal considerably 😊 Where you're right and I was wrongThe consumer API point is fair. I was looking at the internal DOM output and conflating implementation details with the public API. The React component surface is clean – The BEM + The native CSS nesting point is something I hadn't fully articulated, and it's a real practical concern. In Sass you could write On sub-element naming The middle ground – scoped simple class names like On data attributes The CUBE-lens distinction here is useful and cleaner than anything I put in the original post: On CSS file organisation Agreed – one file per component imported into global CSS is the right approach for now. The What I'd propose as a concrete next step Try the simpler scoped sub-part names on new components going forward and see how it feels in practice. Keep the bigger architectural questions – The direction I care about – aligning with where CSS is heading, reducing internal complexity, cleaning up the consumer API – is largely compatible with the foundation that's already in |
Beta Was this translation helpful? Give feedback.
-
Refining the data-attribute heuristic — decisions vs configurationComing back to this with a sharper formulation. The "if the consumer never sets it, it shouldn't be a data attribute" rule works as a first cut, but it covers two structurally different kinds of attributes — and the conflation makes it ambiguous in practice. Two kinds of internal data attributesConfiguration — passes a styling decision through the DOM that the component already knows.
The component already knows what font and spacing it wants — that's a CSS concern, not a DOM concern. Resolving these attributes back into styles is internal coordination dressed up as state. This is the pattern the original post called out. Decision / utility opt-in — interface to shared design system infrastructure.
These don't pass styling through the DOM. They apply a named, shared behavior. Conceptually they're closer to utility classes ( A sharper framing
This catches the original concern (don't leak Figma's variable collection into the DOM) without ruling out legitimate uses of data attributes as hooks into shared infrastructure. Applying it to what's in
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Designing with CSS, not around it 🎨
How modern CSS features replace the workarounds that design system methodologies were built to provide — and what that means for EDS.
🧱 Every major CSS methodology was in essence a polyfill
BEM, CUBE CSS, utility-first, CSS-in-JS — these weren't best practices. They were workarounds for a language that couldn't yet do what we needed. The language has caught up.
Important
The core argument: methodologies existed because CSS wasn't expressive enough. Now that it is, we have an opportunity to build a design system that works with CSS rather than around it.
The three pillars that drove every methodology
🔒 Scoping
CSS is global — everything leaks. BEM naming and CSS Modules existed to prevent collisions. Now we have
@layer, nesting, and Shadow DOM.🧮 Logic
CSS couldn't compute. Sass mixins and JS-in-CSS filled the gap. Now we have
@function,calc(),clamp(), andif().🎨 Theming
There was no native way to switch visual contexts. Theme classes and CSS-in-JS providers emerged. Now custom property inheritance does the job natively — the browser's own cascade handles propagation that JavaScript frameworks have been doing unnecessarily for years.
🗺️ The full replacement map
@layer, nesting, Shadow DOM@function,calc(),clamp(),if()if(),:has(), data attributes@propertywith typed syntaxclamp(),@functionfor steps:has()text-box: trim-both ex alphabeticTip
:has()is arguably the single most transformative CSS addition in years — it lets a parent style itself based on its children, replacing a whole category of JavaScript workarounds.✨ The opportunity
Designsystemet is on a similar path — their button component is a useful reference point.
🏗️ EDS 2.0 Architecture
Two distinct worlds, one shared token contract.
🪙 Design Tokens
Token Studio → Style Dictionary → CSS custom properties. The shared contract between all layers. Typed selectively via
@propertywhere animation or type safety matters.🌊 Page layer (Inspired by CUBE CSS)
Native elements, layout, base typography. CUBE CSS inspired methodology embraces the cascade here — it's exactly the right tool for this context.
l-prefix for layout, global element styles, token inheritance. 1Note
CUBE CSS's "embrace the cascade" philosophy applies to the page layer only. Inside web components, Shadow DOM provides encapsulation — CUBE CSS and the Shadow DOM are philosophically opposite, and that's fine. They serve different contexts.
🧩 Web Components
Shadow DOM encapsulation. Single class per component (
eds-button). Tokens pierce the shadow boundary via custom properties. Adopted stylesheets for zero style duplication across instances.⚛️ React (Phase 1)
Same CSS, light DOM. React becomes a thin wrapper around web components in Phase 2. Product teams never notice the transition.
🧩 Component conventions
One class. Data attributes for consumer decisions only.
The rule of thumb: if removing the attribute would require the consumer to make a new decision — keep it. If removing it means the component handles it internally — remove it.
Caution
The current button exposes the typography system's internal configuration as HTML attributes. This leaks Figma's variable collection structure into the DOM and makes the component hard to reason about for both developers and AI agents. Typography decisions belong inside the component CSS.
Inspired by Designsystemet, whose button in production looks like this:
💅 CSS conventions
One root selector. Nesting for everything else.
Tip
The
eds-overrideslayer at the top of the stack is a sanctioned place for product teams to put customisations. Without it, teams reach for!importantor increasingly specific selectors. With it, overrides become visible feedback — if a product is heavily usingeds-overrides, that's a signal the component or token contract needs attention.Web component factory function
🚀 Migration strategy — same CSS, three phases
Phase 1 — Now: React components
Ship React components using the new CSS conventions. Teams get components immediately. The CSS is the stable contract.
Phase 2 — Autumn: Web components
Same CSS file, adopted into Shadow DOM. React wraps the web component. Consumer API unchanged. Zero visual regression.
Phase 3 — Ongoing: Framework-agnostic adoption
Teams choose their adoption level:
This means teams using Vue, Angular, Svelte, or vanilla HTML all have the same options. EDS works alongside Tailwind too — configure Tailwind to use EDS tokens as its design scale.
Note
A team that starts with just the CSS can migrate to web components later with zero visual regression — they've been building against the same token contract all along.
🪙 Token pipeline
One source. Multiple consumers.
@propertyImportant
Figma is no longer the source of truth for tokens. It becomes a visualisation and component design tool — a consumer of tokens rather than their origin. Token Studio is where tokens are authored. This is a healthier separation of concerns and decouples EDS from any single design tool.
Why
@propertyselectively, not wholesaleTyping every token has a measurable memory and parse cost at scale. Type selectively:
calc()where<length>or<number>typing helps⏳ Progressive enhancement — space toggles now,
if()when readyToday's space toggle pattern is clever but opaque — it exists because CSS lacked conditional logic.
if()is already in Chrome Canary and moving fast.The
@supportsbridge makes the transition automatic — no breaking change, no migration:Tip
Our users are on evergreen browsers — Edge updates within approximately one week of a new Chromium release. This means we can use modern CSS now, not in two years.
@layer,@property,:has(), container queries,text-box,color-mix()— all available today.🛠️ Tooling & enforcement
Stylelint config (updated — BEM is gone)
CLAUDE.md rules for AI agents
✅ Summary — what we're building toward in EDS 2.0
🪙 Tokens in Token Studio
Designers own tokens. Style Dictionary transforms for CSS and Figma. MCP server for AI agents.
🌊 CUBE for the page layer
Embrace the cascade for native elements.
l-for layout, global base styles, token inheritance.🧩 Single class per component
Inspired by Designsystemet. Nesting for descendants. Data-attributes for consumer decisions only.
⚛️ React first, web components next
Same CSS throughout. React wraps web components in Phase 2. Teams never notice the transition.
⏳ Progressive enhancement
Space toggles today.
if()via@supportswhen browser support arrives. No migrations needed.🌐 Evergreen baseline
Edge updates within a week of Chromium. We can build for the web as it is today.
Note
This document is a living record of architectural decisions for EDS 2.0. Discussion and questions welcome below. 👇
Footnotes
Layout classes is something product teams have asked for. ↩
Beta Was this translation helpful? Give feedback.
All reactions