AB#1612078 — Data-attribute theme scoping for type CSS custom properties #293
jason-capsule42
started this conversation in
Post Mortems
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
AB#1612078
Executive Summary
WebCoreStyleSheets (WCSS) previously shipped each visual theme's typography settings as a
separate stylesheet with no way to switch typography themes within a single page. This work
adds a standard "data attribute" hook so a consumer can activate any theme's typography on
part of a page — and bundles every theme's typography into each themed stylesheet so switching
between them no longer requires loading multiple files. The change is additive for typography:
existing pages render exactly as before unless they opt in to the new hook. It mirrors the same
scoping convention already shipped by AuroDesignTokens, keeping the two systems aligned.
A follow-up hardening pass closed a release-blocking gap that would have broken every downstream
Sass consumer on the next publish, and removed leftover files so the shipped package reflects the
new architecture cleanly.
Two follow-on decisions were made during the work at the team's direction: the Auro 2 theme
was fully removed from the build, and the Auro 1 theme's attribute code was set to
atm.Ticket: AB#1612078
The Problem
WCSS builds one bundled stylesheet per theme (Alaska, Alaska Classic, Auro 1, Auro 2, Hawaiian).
Each file exposed its typography settings under a single global scope, so there was no way to say
"use Alaska typography here, Hawaiian typography there" on the same page, and no way to switch a
theme's typography without swapping out the whole stylesheet. AuroDesignSystem's tokens already
solved this with a per-theme attribute selector; WCSS had no equivalent, leaving the two systems
inconsistent for anyone using both.
Root Cause
The type custom properties were emitted under a flat, unscoped
:root {}block bygenerate-theme-type-css-varsinsrc/type/mixins/_type-generator.scss, invoked once per themefrom
generate-themeinsrc/type/mixins/_theme-generator.scss. Because the only selector was:root, there was no attribute-based hook a consumer could target, and each themed bundle(
src/bundled/themes/theme.global.template.scss→dist/bundled/themes/*.global.css) containedonly its own theme's variables. Nothing in the pipeline knew each design-tokens theme's short
"code," so it could not construct the
[data-aag-theme=…]selectors that AuroDesignTokens uses.A second, latent cause surfaced in review: the fix depends on a generated, git-ignored Sass
partial (
src/type/mixins/_theme-codes.scss). Nothing regenerated it at publish time, and the CIrelease job restores a build cache that does not include
src/. On a freshnpm publishthe filewould be physically absent from the working tree, so — although
.npmignoredoes not exclude it —npm would ship a package missing the partial, and every downstream Sass consumer would hit a fatal
Can't find stylesheet to import: theme-codes.The Fix
scripts/theme-codes.build.mjs, readsTHEME_DEFINITIONSfrom@aurodesignsystem/design-tokensand writes a Sass map partial(
src/type/mixins/_theme-codes.scss, git-ignored) mapping each theme directory to its code.This keeps WCSS aligned with design-tokens instead of drifting from a hand-maintained list.
src/type/mixins/_type-generator.scssgainedwcss-type-scope-selector($theme-name, $include-root), which emits both the base[data-aag-theme="aag-theme-<code>"]and the-typesuffixed selector, optionally prefixedwith
:rootfor the file's default theme. A newgenerate-multi-theme-type-css-varsmixinemits the default theme under
:root+ its attributes and every other theme under itsattributes only.
src/bundled/type/themes/<theme>.scssplus theaggregated
src/bundled/type/themes/_all-theme-configs.scsscompile todist/bundled/type/themes/<theme>.css(+.min) via a loop added toscripts/type.classes.css.build.mjs. The global template(
src/bundled/themes/theme.global.template.scss) was updated to import the multi-theme typevars (all themes) plus the theme-agnostic typography classes, so each
*.global.cssnowcarries every theme's typography with its namesake as the
:rootdefault.prepack). Aprepackscript (node scripts/theme-codes.build.mjs) wasadded to
package.json, so npm regenerates the git-ignored partial into the working treeimmediately before packing during
npm publish(which semantic-release invokes). Thisguarantees the file is present in the published package regardless of what the CI build cache
contains — closing the release-blocking gap above.
build:theme-codesruns first in thebuildand test flows so thegenerated map exists before any Sass compile. The duplicate invocation was removed from
build:dist-bundle(which is only ever called frombuild), so the codegen no longer runstwice per build.
tests/typeThemeScoping.spec.scss(sass-true) asserts the scoped selectors forAlaska, Hawaiian, and Auro 1, the
:root-only fallback for an unknown theme with:rootincluded, and the same unknown-theme fallback with
:rootexcluded (the null-code branchshort-circuits before
$include-rootis consulted).src/bundled/type/themes/(alaska/,alaska-classic/,auro-1/,hawaiian/— each withaccent/body/display/headingfiles using the older@use … as v+ individual-configpattern) were deleted; nothing in the codebase imported them, and they would have shipped in the
package alongside the new flat bundles and misled maintainers.
_all-theme-configs.scssgained amaintenance comment noting it is a static manifest while theme discovery elsewhere is
dynamic, so adding a theme requires updating it by hand or the multi-theme bundles silently omit
the new theme.
scripts/excluded-themes.mjsfilter removesAuro 2 from all three theme-driven build scripts; all Auro 2 source directories/partials and
its entries in
_all-theme-configs.scssandsrc/config/_index.scsswere deleted.scripts/theme-codes.build.mjsincludes aTHEME_CODE_OVERRIDESmap setting Auro 1's emitted code toatm(rather than design-tokens'a1), so its selectors areaag-theme-atm/aag-theme-atm-type.Why This Works
The
:rootoutput is retained for every theme, so the cascade for existing consumers isunchanged — the attribute selectors are purely additive and only take effect when a consumer sets
data-aag-themeon an element. Deriving the selector from design-tokens' ownTHEME_DEFINITIONSmeans the WCSS attribute value matches AuroDesignTokens for the same theme, so both systems can be
driven by one attribute. Because each theme's type-config Sass variables are uniquely named, all
themes' variables coexist in one file without collision, which is what makes the multi-theme
"switch within one stylesheet" output safe. The typography classes are theme-agnostic (they
reference the custom properties), so emitting them once per bundle keeps output correct and
non-duplicated.
The
prepackstep is what makes the "non-breaking for existing consumers" guarantee actuallyhold at publish time: because the generated partial is regenerated from the source of truth right
before packing, it is deterministic and cannot go stale, and Sass-source consumers can import the
type mixins on a fresh install without a compile error. Centralizing both the exclusion list and
the code override in single, commented locations keeps the theme list otherwise fully derived from
design-tokens.
Outcome
single page via the
data-aag-themeattribute, matching AuroDesignTokens' convention.consumers compile cleanly on the next release (the release-blocking gap is closed).
bundle architecture without misleading dead files.
atmattribute code.Ticket Completeness
3 of 4 acceptance criteria resolved; 1 partial (color decision deferred without a TRD). Note
that scope was intentionally broadened beyond the ticket (all themes scoped, not just three;
multi-theme-per-file output; Auro 2 removed; Auro 1 code overridden), at the team's direction
during the work.
Resolved
:root. Delivered viawcss-type-scope-selector()and the multi-theme mixin;:rootoutputretained for all themes.
are additive;
:rootblocks are unchanged. Theprepackfix ensures this holds at publish timefor Sass-source consumers, who would otherwise have hit a fatal missing-import error.
Added in
tests/typeThemeScoping.spec.scss(passing), including both unknown-theme fallbackpaths.
Not Resolved / Partial
Color was correctly treated as out of scope (WCSS bakes color to literals at compile time, so
there is nothing to attribute-scope yet), but no TRD was produced to formally document the
decision — it lives only in the ticket and this session. A follow-up TRD/ticket is still owed.
scoping is non-breaking, but two team-directed additions are breaking beyond the ticket: any
consumer importing
type/themes/auro-2(now deleted) or relying on Auro 1'sa1/aag-theme-a1selectors (now
atm) is affected. These fall outside the original acceptance criteria and wereexplicit later requests.
Learnings
THEME_DEFINITIONSat build time avoids a hand-maintained list drifting out of alignment — thesame principle applied to the exclusion filter and the code override, each centralized in one
place.
prepack/prepublishOnlyhook to actually ship. It isnot enough that
.npmignoreallows the file andsrc/is published — npm only packs what isphysically in the working tree at pack time. CI restores a cache that excludes
src/, so thegenerated partial was absent on a fresh release. Regenerating it in
prepack(notpostinstall,which is excluded from the package and would break consumer installs) is the reliable fix.
_all-theme-configs.scssis hand-maintained while the build scripts discover themes dynamically; without a comment
flagging that, adding a theme would produce incomplete multi-theme output with no build error. A
visible maintenance note is the cheapest guard.
were superseded by the flat bundles but left behind; they shipped in the package and invited
confusion. Removing them as part of the change keeps the published surface honest.
*.global.cssfilessingle-theme; the intended deliverable was multi-theme output under
dist/bundled/type/*.Pinning down "which files, what shape" before building would have avoided a rework.
wanted the attribute naming aligned with AuroDesignTokens (which uses
a1for Auro 1), but theteam chose
atm. That divergence is intentional but worth flagging for anyone using bothsystems, since it defeats the "one attribute drives both" benefit for that theme.
a value the tokens didn't support, verifying against
THEME_DEFINITIONS(rather than trustingthe edit) surfaced the conflict for an explicit decision instead of silently encoding a wrong
value.
Iterations That Didn't Work
selectors just to
dist/bundled/themes/*.global.css, each carrying only its own theme. Thiswas rejected: the deliverable needed the scoped selectors in
dist/bundled/type/*and neededevery theme included per file for runtime switching. Superseded by the multi-theme mixin and the
per-theme entry files.
src/is published. An early reading concludedthe theme-codes partial would ship simply because the package includes
src/. Review disprovedthis — the file is git-ignored and absent from CI's restored cache at publish time — and the
prepackregeneration replaced that assumption.a1, then asatm. design-tokens defines Auro 1's code asa1, and anearly test edit expected an unrelated
atmvalue; that assertion was first corrected back toa1after validating the tokens. The team then explicitly requiredatmas an intentionaloverride, which was implemented via
THEME_CODE_OVERRIDESand the spec updated to match.All reactions