- Status: Proposed (accept after the bundle has been exercised on the beta line — see validation plan below)
- Date: 2026-07-20
- Decision makers: Frida Erdal, EDS Core Team
ADR-0009 left one
consequence open: what CSS export surface should eds-tokens 3.0.0 offer at
the flip — the beta release that drops the legacy build/ output and moves
the Tokens Studio output onto the final specifiers?
The Tokens Studio pipeline generates granular, attribute-scoped CSS files
(src/tokens/css/{colors,color-scheme,semantic,primitives,font,density}/*,
nine files, ≈ 44 kB unminified), currently published under the temporary
./next/css/* wildcard. Legacy 2.x publishes exactly one CSS entry:
./css/variables → variables.min.css, a bundle of everything. 2.x has
no granular CSS exports at all — granular paths exist only for the data
formats (./json/*, ./js/*, ./ts/*). No consumer has ever assembled
EDS token CSS from parts.
Facts verified against the repo (2026-07-20):
- Publishing granular files is an API commitment. Export paths cannot be removed or renamed without a breaking change. Publishing the nine files freezes the internal file layout into public API while the token architecture is still being reworked in Figma. Keeping them internal preserves freedom to reorganize.
- Payload is not a differentiator. Full nine-file bundle: 4.6 kB minified + gzipped. The minimal subset (light-only, one density): 3.7 kB. Subset loading saves under 1 kB.
- Component consumers never see the specifiers.
eds-core-react's rollup build inlines the token CSS (postcss-import) into its shippeddist/**/index.css. Only teams importing the tokens package directly are affected. - CSS variables fail silently. A consumer who omits one required file
from a granular set (e.g.
color-scheme/light.css) gets no error — semantic tokens resolve to nothing and the UI renders unstyled. Several import lines are several chances to get this wrong without a signal. - The new output is attribute-mandatory, unlike 2.x. The scheme
aliases exist only under
[data-color-scheme="light"|"dark"]; without the attribute nothing resolves — not even light mode. The 2.x bundle works with no attribute (light values at:root, dark via explicit attribute, plus aprefers-color-schememedia fallback appended bybuild-dark-scope— added in #4864 to preserve the system-dark behaviour thatlight-dark()had provided implicitly).
- The one-import habit (
@import '@equinor/eds-tokens/css/variables') is the only import pattern EDS consumers have ever had - Smallest possible public API surface: keep the generated file layout an internal pipeline detail while the token architecture is in motion
- No demand for granular CSS exists, and the measured payload gain of subsetting is negligible
- A semver major legitimizes changing the content behind a kept specifier; consumers upgrading across a major expect breaking changes and read the migration guide
Pros: no bundle step; consumers load only the modes they use.
Cons: every direct consumer needs six-plus @import lines and must
learn which files are required versus opt-in, with silent failure as the
error mode; breaks the only established import habit; freezes the internal
file layout into public API; measured payload gain under 1 kB.
Pros: one-import DX preserved; payload-sensitive consumers keep subsetting.
Cons: still publishes the granular layout as permanent API (the real cost of A) for a benefit measured at under 1 kB; two documented ways to do the same thing.
Mirror 2.x: publish a single generated bundle as ./css/variables; the
granular files remain committed pipeline artifacts that are not exported.
Pros: identical consumer surface to 2.x (the variable names change, the shape of the package does not); internal file layout stays free to change; no subset footgun; shortest possible setup docs.
Cons: consumers cannot skip unused modes (costs < 1 kB); a blind 2.x → 3.0 upgrade keeps resolving the import and fails visually instead of at build time (mitigated: with every variable renamed, the unstyled result is unmissable, and the major-version migration guide is the intended path).
Option C. eds-tokens 3.0.0 publishes one bundled CSS entry on the
reused ./css/variables specifier, mirroring the 2.x export surface. The
granular files stay internal.
- Name reuse is deliberate. ADR-0009's "path continuity is misleading" argument applied to flipping specifiers mid-migration, where unmigrated components would break silently on install. At a major version boundary the same continuity is a feature: one import line, one habit, content change signalled by the major bump and the migration guide.
- Where the bundle is built: a bundle step in
tokens_studio_release.yaml, committed as generated output alongside the nine source files (inspectable in the release workflow's automated PRs like everything else insrc/tokens/). Concatenation is conflict-free by construction — every mode file is attribute-scoped. The step is a plain concatenation, deliberately not minified: the committed bundle stays a pure function of the source files (no minifier-version churn in release-PR diffs, no extra toolchain dependency in the pipeline) and diffs stay reviewable. Gzip closes the size gap (4.8 vs 4.6 kB); consumers that bundle minify with their own tooling. The bundle's only added behaviour is the@layer eds-tokenswrap decided in ADR-0014 (proposed in #5178, decision point 6; not yet implemented — the shipped bundle is unlayered until then). The wrap is deterministic, so the bundle stays a pure function of the source files; beyond it the step adds nothing on top of the generated files (see the colour-scheme note below). - Beta validation costs nothing extra: the existing
./next/css/*wildcard maps tosrc/tokens/css/*, so the committed bundle is importable as@equinor/eds-tokens/next/css/variables.cssin the next beta with no publish-workflow change. The temporarynext/*paths (including the granular files) disappear at the flip as ADR-0009 already prescribes.
The bundle keeps the generated output's attribute-scoped behaviour as-is:
consumers set data-color-scheme once at root level, per the theming
convention decided in
ADR-0004. There is no
prefers-color-scheme media fallback in 3.0. 2.x behaves differently
(light at :root, automatic system-dark via a media fallback), but that
behaviour is inherited from light-dark() in the original 2024 build
setup (#3447) and a bug-fix rewrite that preserved its semantics (#4864),
not from a design decision — it does not carry over. Apps that want to
follow the OS make that choice themselves by setting the attribute (e.g.
from a matchMedia listener); the design system provides the switch, the
app decides the policy.
- Whether anything beyond colour needs mode files bundled as the token architecture rework lands (density set may change shape).
- Add the concat step; the bundle ships in the next beta as
next/css/variables.css - Use it in the
/nextcomponent migration and let beta testers exercise it - Settle the open questions; at the flip, map
./css/variablesto the bundle per ADR-0009's exit plan - Mark this ADR Accepted when at least one beta has exposed the exact 3.0.0 surface
- Good, because 2.x consumers keep their single import line through the major upgrade; only variable names migrate
- Good, because the granular file layout stays changeable while designers rework the token architecture
- Good, because the bundle is validated on the beta line before the flip, matching ADR-0009's exit plan
- Bad, because the pipeline gains a small generated artifact whose build
step must follow future changes to the export set (mitigated: the step
should glob
src/tokens/css/, not hard-code file names) - Bad, because 2.x's automatic system-dark does not carry over: apps that
relied on it must set
data-color-schemethemselves — the migration guide must call this out explicitly
- ADR-0009 — the open consequence this ADR settles
- ADR-0004 — the theming
convention (
data-color-schemeset once at root by the consumer) that governs the bundle's colour-scheme behaviour - ADR-0013
— why the token CSS uses attribute-scoped rules and never
light-dark() - ADR-0014 (proposed in
#5178) — the
output architecture; decides the
@layer eds-tokenswrap this bundle step applies - PR #4864 — why the
2.x bundle has explicit dark scopes + the
prefers-color-schemefallback (build-dark-scope) .github/workflows/tokens_studio_release.yaml— where the concat step will live- Epic #4740; issue #5108