Skip to content

feat: add Meta Theme - #692

Merged
rubyycheung merged 4 commits into
mainfrom
navi/feat/meta-theme
Mar 27, 2026
Merged

feat: add Meta Theme#692
rubyycheung merged 4 commits into
mainfrom
navi/feat/meta-theme

Conversation

@rubyycheung

@rubyycheung rubyycheung commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Meta Theme for XDS

New theme package @xds/theme-meta — Meta's brand identity for XDS, with all color tokens sourced from CDS XMDS 3.0.

Token Comparison: XDS Default → Meta (CDS)

Core
Token XDS Default (light) Meta / CDS (light) XDS Default (dark) Meta / CDS (dark) CDS Source
--color-accent #0064E0 #0044A3 #2694FE #0058CD BLUE_BADGE → BLUE_650 / BLUE_550
--color-accent-deemphasized #0082FB33 rgba(0,68,163,0.1) #0082FB3F rgba(0,88,205,0.15) Derived BLUE_650 @10% / BLUE_550 @15%
--color-accent-text #0143B5 #FFFFFF #4BA9FE #FFFFFF PRIMARY_BUTTON_TEXT → WHITE
Surfaces
Token XDS Default (light) Meta / CDS (light) XDS Default (dark) Meta / CDS (dark) CDS Source
--color-surface #FFFFFF #FFFFFF #1F1F22 #1F1F22 SURFACE_BACKGROUND → WHITE / GRAY_1050
--color-wash #F1F4F7 #F2F4F6 #111112 #111112 BACKGROUND_DEEMPHASIZED → GRAY_50 / GRAY_1100
--color-card #FFFFFF #FFFFFF #1F1F22 #1F1F22 CARD_BACKGROUND → WHITE / GRAY_1050
--color-popover #FFFFFF #FFFFFF #28292C #28292C POPOVER_BACKGROUND → WHITE / GRAY_1000
--color-deemphasized #0536590C #F2F4F6 #1111127F #28292C CARD_BACKGROUND_FILLED → GRAY_50 / GRAY_1000
--color-overlay #01122866 rgba(0,0,0,0.6) #11111299 rgba(0,0,0,0.6) OVERLAY_ON_SURFACE → BLACK_A_60
--color-hover-overlay #0536590C rgba(17,17,18,0.05) #FFFFFF0C rgba(242,244,246,0.05) Derived GRAY_1100 @5% / GRAY_50 @5%
--color-pressed-overlay #05365919 rgba(17,17,18,0.08) #FFFFFF19 rgba(242,244,246,0.08) Derived GRAY_1100 @8% / GRAY_50 @8%
Text
Token XDS Default (light) Meta / CDS (light) XDS Default (dark) Meta / CDS (dark) CDS Source
--color-text-primary #0A1317 #111112 #DFE2E5 #F2F4F6 PRIMARY_TEXT → GRAY_1100 / GRAY_50
--color-text-secondary #4E606F #666A72 #AAAFB5 #9FA4AB SECONDARY_TEXT → GRAY_650 / GRAY_400
--color-text-disabled #A4B0BC #9FA4AB #6F747C #666A72 DISABLED_TEXT → GRAY_400 / GRAY_650
--color-text-link #0064E0 #0044A3 #3E9EFB #3087FF BLUE_LINK → BLUE_650 / BLUE_400
--color-text-placeholder #4E606F #666A72 #AAAFB5 #9FA4AB PLACEHOLDER_TEXT → GRAY_650 / GRAY_400
--color-text-on-media #FFFFFF #FFFFFF #FFFFFF #FFFFFF PRIMARY_TEXT_ON_MEDIA → WHITE
Icons
Token XDS Default (light) Meta / CDS (light) XDS Default (dark) Meta / CDS (dark) CDS Source
--color-icon-primary #0A1317 #111112 #DFE2E5 #F2F4F6 PRIMARY_ICON → GRAY_1100 / GRAY_50
--color-icon-secondary #4E606F #666A72 #AAAFB5 #9FA4AB SECONDARY_ICON → GRAY_650 / GRAY_400
--color-icon-tertiary #748695 #666A72 #8C939B #9FA4AB PLACEHOLDER_ICON → GRAY_650 / GRAY_400
--color-icon-disabled #A4B0BC #9FA4AB #6F747C #666A72 DISABLED_ICON → GRAY_400 / GRAY_650
--color-icon-on-media #FFFFFF #FFFFFF #FFFFFF #FFFFFF PRIMARY_ICON_ON_MEDIA → WHITE
Focus
Token XDS Default (light) Meta / CDS (light) XDS Default (dark) Meta / CDS (dark) CDS Source
--color-focus-outline #0171E3 #0044A3 #2694FE #0058CD BLUE_BADGE → BLUE_650 / BLUE_550
--color-focus-outline-error #E3193B #D31130 #F5394F #FB7D87 NEGATIVE → RED_650 / RED_400
--color-focus-outline-success #0D8626 #147B29 #0D8626 #3CBC22 POSITIVE → GREEN_650 / GREEN_400
--color-focus-outline-warning #F2C00B #965E03 #E9AF08 #D69804 WARNING → YELLOW_650 / YELLOW_400
Dividers
Token XDS Default (light) Meta / CDS (light) XDS Default (dark) Meta / CDS (dark) CDS Source
--color-divider #05365919 #DFE2E5 #F2F4F619 #494D53 DIVIDER → GRAY_150 / GRAY_800
--color-divider-high-contrast #647685 #666A72 #6F747C #9FA4AB Derived → GRAY_650 / GRAY_400
--color-divider-emphasized #CCD3DB #D0D3D6 #494D53 #494D53 Derived → GRAY_200 / GRAY_800
Status
Token XDS Default (light) Meta / CDS (light) XDS Default (dark) Meta / CDS (dark) CDS Source
--color-positive #0D8626 #147B29 #0D8626 #3CBC22 POSITIVE → GREEN_650 / GREEN_400
--color-negative #E3193B #D31130 #F5394F #FB7D87 NEGATIVE → RED_650 / RED_400
--color-warning #E9AF08 #965E03 #F2C00B #D69804 WARNING → YELLOW_650 / YELLOW_400
--color-educational #5B08D8 #0044A3 #6B1EFD #3087FF BLUE_LINK → BLUE_650 / BLUE_400
Utility
Token XDS Default (light) Meta / CDS (light) XDS Default (dark) Meta / CDS (dark) CDS Source
--color-glimmer #CCD3DB #E7EAED #5A5E66 #28292C Derived → GRAY_100 / GRAY_1000
--color-glimmer-high-contrast #A4B0BC #DFE2E5 #8C939B #494D53 Derived → GRAY_150 / GRAY_800
--color-shadow-elevation rgba(17,17,18,0.12) rgba(17,17,18,0.12) ELEVATED_SHADOW → GRAY_1100_A_12
--color-hover-tint black black white white
Named Palette
Color Role XDS Default (light) Meta / CDS (light) XDS Default (dark) Meta / CDS (dark)
Blue background #0171E333 #D7E9FF #0171E333 #001F4C
border #0171E3 #0044A3 #4BA9FE #3087FF
icon #0064E0 #0044A3 #2694FE #3087FF
text #042F97 #003B8E #AFD7FF #4D99FF
Gray background #0A131733 #F2F4F6 #666A724C #28292C
border #647685 #DFE2E5 #8C939B #494D53
icon #4E606F #666A72 #AAAFB5 #9FA4AB
text #0A1317 #111112 #E7EAED #F2F4F6
Green background #24BB5E33 #C4F8B9 #24BB5E33 #053018
border #24BB5E #147B29 #4CD964 #3CBC22
icon #0D8626 #147B29 #26A756 #3CBC22
text #09441F #076D29 #A5F690 #4EC72A
Red background #E3193B33 #FEE4E6 #E3193B33 #5A0107
border #E3193B #D31130 #F5394F #FB7D87
icon #D31130 #D31130 #E3193B #FB7D87
text #7B0210 #BE0424 #FFB2B8 #FD8E99
Orange background #F2790233 #FFE6CF #F2790233 #4E1608
border #F27902 #B34A01 #FFA040 #F88617
icon #E9690B #B34A01 #FB8C00 #F88617
text #6B2203 #A13F04 #FDB876 #FD9537
Yellow background #FFEB3B33 #FCEC85 #FFEB3B33 #451E03
border #FFEB3B #965E03 #FFF176 #D69804
icon #FBC02D #965E03 #FFEE58 #D69804
text #753F07 #8A5001 #FBCE03 #E2A400
Purple background #7952FF33 #ECE2FF #7952FF33 #140036
border #7952FF #5828CA #9575CD #9B73FF
icon #5B08D8 #5828CA #7952FF #9B73FF
text #3E0697 #4D1EB6 #B3B0FE #A985FF
Pink background #E91E6333 #FFE1ED #E91E6333 #520019
border #E91E63 #C71050 #F48FB1 #FE73A1
icon #C2185B #C71050 #EC407A #FE73A1
text #880E4F #B30543 #F8BBD0 #FF85B0
Cyan background #00BCD433 #C7F1FF #00BCD433 #001B2A
border #00BCD4 #006BA3 #4DD0E1 #00AFFA
icon #00ACC1 #006BA3 #26C6DA #00AFFA
text #006064 #005F91 #B2EBF2 #21BDFF
Teal background #0DB7AF33 #BCF5F0 #0DB7AF33 #062D38
border #0DB7AF #08767D #4DB6AC #0DB7AF
icon #009688 #08767D #26A69A #0DB7AF
text #083943 #0F686F #40DCCD #1DC3B9

Methodology: How tokens were mapped

Token values were derived through semantic mapping — not closest visual match.

Step 1: CDS semantic token assignments

The source of truth is CDSDefaultThemeSource.php, which explicitly maps every CDS semantic token to XMDS 3.0 color names with light/dark pairs. For example:

CDSThemeColors::PRIMARY_TEXT => DspToken::semanticValue(shape(
    'light_color_name' => CDSColors::XMDS_3_GRAY_1100,  // #111112
    'dark_color_name'  => CDSColors::XMDS_3_GRAY_50,    // #F2F4F6
))

Step 2: CDS semantic → XDS semantic (by meaning)

Each XDS token was matched to its CDS equivalent by what it means, not what it looks like:

XDS Token CDS Semantic Token Why
--color-accent BLUE_BADGE CDS docs: "Primary brand accent color (brand blue)" — same role as XDS accent
--color-text-primary PRIMARY_TEXT Direct semantic match
--color-text-secondary SECONDARY_TEXT Direct semantic match
--color-text-disabled DISABLED_TEXT Direct semantic match
--color-text-link BLUE_LINK Direct semantic match
--color-surface SURFACE_BACKGROUND Direct semantic match
--color-wash BACKGROUND_DEEMPHASIZED Both mean "receded background for secondary areas"
--color-card CARD_BACKGROUND Direct semantic match
--color-popover POPOVER_BACKGROUND Direct semantic match
--color-negative NEGATIVE Direct semantic match
--color-positive POSITIVE Direct semantic match
--color-warning WARNING Direct semantic match
--color-divider DIVIDER Direct semantic match
--color-icon-primary PRIMARY_ICON Direct semantic match
--color-focus-outline BLUE_BADGE CDS uses brand blue for focus states

Step 3: Resolve to hex via CDSBaseColorDefinition

XMDS 3.0 color names were resolved to RGB values from CDSBaseColorDefinition.php:

XMDS_3_GRAY_1100 → rgb(17, 17, 18)   → #111112
XMDS_3_GRAY_50   → rgb(242, 244, 246) → #F2F4F6
XMDS_3_BLUE_650  → rgb(0, 68, 163)    → #0044A3

Step 4: Named palette — consistent ramp pattern

CDS doesn't have direct equivalents for XDS's --color-{hue}-background/border/icon/text tokens. These were filled using a consistent ramp position pattern derived from how CDS uses its scales for semantic tokens:

Role Light mode Dark mode Rationale
Background _100 _1000 Lightest/darkest tints for subtle fills
Border & Icon _650 _400 Same stops CDS uses for primary semantics (e.g. NEGATIVE = RED_650/RED_400)
Text _700 _350 One step darker/lighter than border for readability

This pattern is consistent across all 10 hue families (blue, gray, green, red, orange, yellow, purple, pink, cyan, teal).

What's included

Color palette — sourced from CDS XMDS 3.0:

  • All color tokens derived from CDSBaseColorDefinition.php (XMDS 3.0 ramp)
  • Semantic assignments follow CDSDefaultThemeSource.php mappings
  • Primary brand accent: BLUE_650 (#0044A3) / BLUE_550 (#0058CD)
  • Surfaces: WHITE / GRAY_1050 (light/dark), GRAY_50 / GRAY_1100 (wash)
  • Text hierarchy: GRAY_1100/GRAY_50 (primary), GRAY_650/GRAY_400 (secondary)
  • Status colors: RED_650, GREEN_650, YELLOW_650 (light) with _400 variants (dark)
  • Full named palette (blue, gray, green, red, orange, yellow, purple, pink, cyan, teal) using 650 for border/icon, 700/350 for text, 100/1000 for background
  • Shadow tokens use GRAY_1100_A_12 (elevated) and GRAY_1100_A_16 (persistent)
  • Every token has a CDS reference comment tracing back to the source XMDS_3_* color name

Typography:

  • System font stack (-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, ...)
  • Clean heading hierarchy with semibold/bold weights

Radius:

  • Moderate rounding (pill: 9999px)

Components:

  • Button: white-on-accent primary, GRAY_50/GRAY_1000 secondary
  • Card & Section: consistent 16px padding
  • Heading: 6 levels
  • Text: body, large, label, supporting, code
  • Badge: semibold weight

Icons:

  • Meta icon registry (metaIconRegistry)

CDS source files

  • internal monorepo/.../cds/CDSBaseColorDefinition.php — raw XMDS 3.0 RGB values
  • internal monorepo/.../cds/themes/CDSDefaultThemeSource.php — semantic token → color mappings
  • internal monorepo/.../cds/CDSThemeColors.php — semantic token enum

@rubyycheung
rubyycheung requested a review from cixzhang as a code owner March 18, 2026 21:11
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Mar 18, 2026
@rubyycheung
rubyycheung marked this pull request as draft March 18, 2026 21:16
@github-actions

github-actions Bot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@xds/core 11.2KB 17.7KB 2.7KB

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@rubyycheung
rubyycheung force-pushed the navi/feat/meta-theme branch from 4b33f96 to cced001 Compare March 19, 2026 04:20
@rubyycheung

Copy link
Copy Markdown
Contributor Author

Updated: Theme Override Issues — Summary for @cixzhang

What we were trying to do

Build a Meta Theme with color tokens sourced from CDS XMDS 3.0 (CDSBaseColorDefinition.php + CDSDefaultThemeSource.php). Every token maps to an actual CDS color — accent from PRIMARY_BUTTON_BACKGROUND (Meta Blue #0064E0), text from PRIMARY_TEXT, surfaces from SURFACE_BACKGROUND, etc.

What works ✅

  • Component overrides — pill buttons, font family (Figtree), heading styles
  • Theme CSS generationdefineTheme() produces correct CSS

What doesn't work ❌

  • Token color overrides are silently ignored — the theme sets --color-wash: #F2F4F6 but the Storybook still shows #F1F4F7 (XDS default). Same for all other color tokens. The compat CSS with doubled selectors still isn't winning.

What threw us off

The Storybook token panel (left sidebar) always shows the base :root values from StyleX defineVars, not the theme-resolved values. So when we changed --color-accent to #0064E0 (Meta Blue), the panel showed #0064E0 — but that's because it's the same as the XDS default, not because the theme override worked. The panel was misleading us into thinking things were fine when other tokens (wash, overlay, surface) clearly showed defaults.

This means:

  1. The token panel is not theme-aware — it reads from :root, not [data-xds-theme]
  2. You can't visually verify theme token overrides from the panel
  3. The only way to tell if tokens are working is to use values that are visibly different from defaults

Root cause (unchanged)

StyleX defineVars outputs custom properties on :root outside any CSS layer (unlayered). Theme overrides — whether via @scope/@layer (runtime), static CSS import, or doubled attribute selectors — all lose to :root because:

  1. @layer loses to unlayered — CSS cascade spec: unlayered > layered, always
  2. Source order — Vite places theme CSS before StyleX CSS in the bundle; later wins at equal specificity
  3. !important stripped — LightningCSS removes !important from custom property declarations
  4. Doubled selector (0,2,0) vs :root (0,1,0) — should win by specificity, but may still lose due to source order or CSS processing

Suggested core fixes

  1. Put StyleX defineVars output into @layer xds.base so theme CSS (layered or not) can override it
  2. Or: generate theme CSS with doubled selectors in generateThemeCSS instead of @scope :scope
  3. Or: make the Storybook token panel read computed values from the themed element instead of :root

This is a systemic issue affecting all XDS themes that override color tokens, not just Meta.

@rubyycheung

rubyycheung commented Mar 19, 2026

Copy link
Copy Markdown
Contributor Author

Addendum: Accent color mapping error

During the initial CDS-to-XDS token mapping, I incorrectly used BLUE_BADGE (BLUE_650 / #0044A3) for XDS's --color-accent token. The CDS docs describe BLUE_BADGE as "Primary brand accent color (brand blue)", which made it sound like the right choice.

However, BLUE_BADGE is actually the blue used for notification badges and toggles — it's a darker navy (#0044A3). The actual Meta Blue used for primary action buttons is BLUE_500 (#0064E0), which is what CDS maps to PRIMARY_BUTTON_BACKGROUND.

This was corrected to #0064E0 (light) / #2694FE (dark), but the coincidence that #0064E0 is also the XDS default accent color made it harder to tell whether the theme override was actually working — contributing to the debugging confusion described above.

Lesson: CDS token names are scoped to specific component contexts (BLUE_BADGE = badge component, PRIMARY_BUTTON_BACKGROUND = button component). For mapping to XDS's generic semantic tokens like --color-accent, look at the button/action tokens, not the badge tokens.

image

@rubyycheung

rubyycheung commented Mar 20, 2026

Copy link
Copy Markdown
Contributor Author

PR #692 Analysis — Theme Infrastructure Evaluation

This PR creates @xds/theme-meta as a stress test of the XDS theme system. Here is a breakdown of what worked purely through the theme API vs what required core changes.


Approach & learnings

The initial approach was to derive the Meta theme directly from the CDS (Core Design System) source files in WWW (CDSBaseColorDefinition.php, CDSDefaultThemeSource.php). However, this couldn't produce good outputs — the CDS token structure doesn't map 1:1 to XDS tokens, and the generated values didn't match what the actual Meta products look like.

The next step was to manually set theme tokens (colors, radius, typography) through defineTheme(). This worked well for global values, but the real insight came from iteration: designers and engineers tend to think in terms of individual components, not global tokens. They want "the radio should look like this" or "the dialog header should be semibold" — not "change --color-border-emphasized globally."

This meant the bulk of the work wasn't token overrides — it was per-component visual tweaks that the theme system sometimes couldn't express without touching core.


By the numbers

Category Files Lines
Theme package (@xds/theme-meta) 6 +616
Core — CSS custom properties (with fallbacks) 8 ~30
Core — Structural changes (all themes) 5 ~50
Storybook wiring 6 ~80
Infra / other 6 ~20
Total 31 ~796

What the theme system handled well ✅

Token overrides — 70+ color tokens, radius scale, and typography all defined purely in metaTheme.ts via defineTheme(). Zero core changes needed.

Component style overrides — 12 components customized through components: {}: button, badge, heading, text, card, section, banner, slider, radio, checkbox, switch, dialog, calendar. All via CSS class targeting with no core changes. This includes things like:

  • Button pill radius (borderRadius: var(--radius-rounded))
  • Button font weight per variant
  • Badge font weight
  • Dialog scoped heading weight
  • Card/section padding

However, the button radius override required !important (borderRadius: 'var(--radius-rounded) !important') because StyleX's inline styles have higher specificity than the theme's class-based CSS overrides. This is a minor but recurring friction point — any theme that needs to override a StyleX-set property on a component may need !important to win the specificity battle.


Where we had to touch core (the gaps) ⚠️

16 CSS custom properties added across 8 core components to make them theme-able. Each follows the pattern var(--xds-component-property, <original-value>) so existing themes are unaffected.

Breakdown by what they control

Category Count % Properties
Color 10 63% radio checked border/bg/dot, unchecked border; checkbox unchecked border; switch off bg; slider track bg; calendar selected bg; dialog bg; banner icon color
Sizing 4 25% radio border width, radio dot size, header border width, footer border width
Spacing 2 12% header bottom padding, footer top padding

Detail per component

Component Vars Added Why the theme system couldn't handle it
RadioListItem 6 Checked/unchecked colors, dot size, border width all hardcoded in StyleX
LayoutHeader 2 Divider border width and padding hardcoded
LayoutFooter 2 Same as header
Calendar 1 Selected date bg hardcoded to --color-accent
CheckboxInput 1 Unchecked border color not overridable
Dialog 1 Background hardcoded to --color-surface
Slider 1 Track bg hardcoded to --color-muted
Switch 1 Off track bg hardcoded

Root cause in every case: a component hardcodes a specific token reference (e.g., colorVars['--color-accent']) in StyleX, and the theme needed a different value in that spot. The theme system can change --color-accent globally, but can't say "use --color-surface for the radio background when checked."

2 structural changes (all themes)

  • Banner: title <p><h3>, default status icon always rendered
  • Button: XDSButtonVariantProp type (XDSButtonVariant | string & {}) so themes can define custom variants (e.g., primary-muted, primary-outline) without modifying core

Key infrastructure observations

  1. The theme system can't target sub-elements. Component overrides set CSS properties on .xds-component but can't reach inner elements (radio dot, slider track, switch track). Every sub-element customization required adding a CSS custom property to core. This was the Adding Code of Conduct file #1 source of core changes.

  2. No escape hatch for extending variants. Adding primary-muted / primary-outline / etc. required a type change and ensuring the component gracefully skips unknown StyleX variants. A first-class mechanism for theme-defined variants would eliminate this.

  3. Hardcoded token references in StyleX aren't per-component overridable. When a component uses colorVars['--color-accent'] directly, the theme can only change --color-accent globally — not per-component. The var(--xds-X, fallback) pattern works but must be manually added per property.

  4. StyleX specificity beats theme overrides. Component overrides use class-based selectors (.xds-button { borderRadius: ... }), but StyleX applies styles at inline-level specificity. This forces !important for property overrides like button radius. A cleaner specificity model between StyleX and the theme layer would eliminate this.

  5. hasDivider is a prop, not a style. Removing borders via CSS (border-width: 0) works, but the padding collapse logic is prop-driven. This disconnect forced us to add padding custom properties too.

  6. Font loading is the consumer's responsibility. The theme declares Figtree but can't load it — we had to add a Google Fonts <link> to Storybook manually.

  7. Designers think in components, not tokens. The initial plan was global token mapping from CDS. In practice, most requests were component-specific: "radio should be ring-style," "dialog header should be semibold," "badge text should be medium." The theme system needs to support this mental model natively.


Recommendations for theme infra improvements

Priority Recommendation Impact
🔴 High Auto-expose sub-element CSS custom properties — components with visual sub-elements should generate var(--xds-X, default) for key visual properties Eliminates ~10 of the 16 core changes
🔴 High First-class custom variants — let themes register new variant definitions that components accept without core type changes Eliminates the XDSButtonVariantProp workaround
🔴 High Resolve StyleX vs theme specificity — ensure theme component overrides can win over StyleX without !important Eliminates !important hacks in theme overrides
🟡 Medium Component-scoped token overrides — allow --color-accent to be overridden only within a specific component scope Cleaner than adding component-specific custom properties
🟡 Medium Font loading integration — themes should declare font URLs that <XDSTheme> loads automatically Prevents the "font declared but not loaded" gotcha
🟡 Medium Support CDS/external design system import — tooling to map external token systems (like CDS XMDS 3.0) to XDS tokens, even if imperfect Reduces manual token translation work
🟢 Nice Style-driven divider control — make border/padding behavior respond to CSS rather than requiring a prop Removes the prop-vs-style disconnect

@cixzhang

cixzhang commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Theme infrastructure follow-ups from this PR

Thanks for the thorough analysis, @rubyycheung — the breakdown of what the theme system handled vs where core changes were needed shaped the theming standards.

Shipped ✅

  • Sub-element xdsClassName targeting#763 (merged). 9 new targets across 6 components (radio/dot, switch-thumb, slider-track/thumb, calendar-day, banner-icon, progressbar-fill). State classes (checked, selected, today, disabled) for state-specific theming via defineTheme. This is the primary mechanism for per-component theming — preferred over CSS vars.

  • Component-level CSS vars#739 (merged). --{component}-{property} vars for values that participate in calc() (concentric radius). 9 vars across 8 components.

  • Extensible variant types#764 (merged). All 13 variant components use VariantMap interface + keyof pattern. Theme packages extend via module augmentation.

  • defineTheme variants field + CLI theme resolution#790. Themes declare custom variants in defineTheme({ variants: { button: ["primary-muted"] } }). CLI resolves active theme from package.json or env var. xds theme build generates .d.ts for TypeScript autocomplete.

  • Theming standards documentedTheming Infrastructure wiki fully rewritten with cascade model, sub-element targeting, state classes, custom variants, font declarations. Component Authoring Guide updated with xdsClassName rules and extensible variant pattern.

In Progress

  • Theme font declarations#788 (draft). fonts field on theme object, runtime fallback loading, CLI warnings.

  • CSS cascade ordering#792. The StyleX specificity issue (!important needed for some overrides). Proposed stylex.defaults() API — RFC filed as facebook/stylex#1539. Interim: doubled selectors + useCSSLayers.

Your findings → resolutions

Finding Resolution
10 of 16 core changes were sub-element colors/sizing xdsClassName sub-element targets + state classes (#763 ✅)
!important needed for StyleX specificity stylex.defaults() RFC (stylex#1539), interim workarounds (#792)
Custom variants need core type change VariantMap interfaces (#764 ✅) + defineTheme variants field (#790)
Font loading is manual Theme fonts field + layered loading (#788 draft)
Storybook token panel not theme-aware Storybook tooling issue, not core
hasDivider prop vs style disconnect By design — themes control appearance, not features
Designers think in components, not tokens Validated by the component overrides system

All 17 of your CSS var needs from the Meta theme are now addressable via xdsClassName class targeting + state classes — no core CSS vars required for sub-element theming.

@rubyycheung

rubyycheung commented Mar 21, 2026

Copy link
Copy Markdown
Contributor Author

Impact of theming infrastructure on this PR

Following up on @cixzhang's theming infrastructure work — here's how those changes reduced the core footprint of this PR.

Before → After

Metric Before After Delta
Core files changed 11 7 -4 files
Core lines added +175 +163 -12 lines
CSS custom properties in core 17 7 -10 vars
Manual heading/text overrides ~60 lines 0 (auto-generated) -60 lines

4 core files fully reverted (zero diff from main)

These no longer need any changes thanks to xdsClassName class targeting:

  • Calendar/styles.ts--xds-calendar-selected-bg.xds-calendar-day.selected { background-color }
  • Dialog/XDSDialog.tsx--xds-dialog-bg.xds-dialog { background-color }
  • Layout/XDSLayoutHeader.tsx--xds-header-border-width, --xds-header-padding-bottom → class targeting
  • Layout/XDSLayoutFooter.tsx--xds-footer-border-width, --xds-footer-padding-top → class targeting

10 CSS vars removed from core

These were introduced as escape hatches but are now handled by xdsClassName sub-element targeting in defineTheme:

Removed CSS var Component Replaced by
--xds-banner-icon-color Banner .xds-banner-icon { color }
--xds-calendar-selected-bg Calendar .xds-calendar-day.selected { background-color }
--xds-dialog-bg Dialog .xds-dialog { background-color }
--xds-header-border-width LayoutHeader class targeting
--xds-header-padding-bottom LayoutHeader class targeting
--xds-footer-border-width LayoutFooter class targeting
--xds-footer-padding-top LayoutFooter class targeting
--xds-radio-dot-color Radio .xds-radio-dot { background-color }
--xds-radio-dot-size Radio .xds-radio-dot { width, height }
--xds-slider-track-bg Slider .xds-slider-track { background-color }

7 CSS vars still needed in core

These participate in color-mix() hover expressions inside StyleX — class targeting can't replace a var that's referenced inside a computed expression:

CSS var Component Why it stays
--xds-radio-border-width Radio border-width in base style
--xds-radio-unchecked-border Radio color-mix(in srgb, var(--xds-radio-unchecked-border, ...), hover-tint 20%)
--xds-radio-checked-border Radio color-mix(in srgb, var(--xds-radio-checked-border, ...), hover-tint 15%)
--xds-radio-checked-bg Radio color-mix(in srgb, var(--xds-radio-checked-bg, ...), hover-tint 15%)
--xds-checkbox-unchecked-border Checkbox color-mix(in srgb, var(--xds-checkbox-unchecked-border, ...), hover-tint 20%)
--xds-checkbox-border-width Checkbox border-width in base style
--xds-switch-off-bg Switch color-mix(in srgb, var(--xds-switch-off-bg, ...), hover-tint 5%)

Theme improvements

  • typeScale + motionScale — auto-generates heading/text component overrides (replaces ~60 lines of manual definitions)
  • variants field — declares 5 custom button variants; xds theme build generates meta.variants.d.ts with TypeScript module augmentation for autocomplete
  • Sub-element targetingbanner-icon, radio-dot, slider-track, calendar-day.selected targeted via @scope'd .xds-* selectors
  • CLI registry updated to recognize sub-element xdsClassName targets (no more "Unknown component" warnings)

Remaining core changes (7 files)

File Change Category
XDSBanner.tsx <p><h3> title, default icon always shown, sm size, per-status color styles Semantic + feature
XDSBanner.test.tsx Updated tests for h3 title and default icon Test
XDSButton.tsx 5 new variant StyleX objects with hover/focus/active states (+105 lines) Feature
XDSCheckboxInput.tsx CSS var for border-width and unchecked-border (hover color-mix) Theming escape hatch
XDSRadioListItem.tsx CSS vars for border/bg (hover color-mix), dot centering fix, radiusVars token Theming escape hatch + bug fix
XDSSlider.tsx Thumb boxShadow, shadowVars import Visual refinement
XDSSwitch.tsx CSS var for off-bg (hover color-mix) Theming escape hatch

cixzhang pushed a commit that referenced this pull request Mar 21, 2026
Adds layering tests to brutalist theme that verify:
- Component overrides (borderRadius, borderWidth, textTransform) work
  without !important when wrapped in @layer xds.theme
- Reproduces the exact pattern from Ruby's Meta theme (#692) where
  borderRadius on button base needed !important to win over StyleX
- Zero !important in entire theme CSS output
- All component overrides present in generated CSS

10 tests covering layer-test theme + full brutalist theme.
cixzhang pushed a commit that referenced this pull request Mar 21, 2026
Adds borderRadius: var(--radius-rounded) to brutalist button base —
the exact pattern that needed !important in Meta theme (#692).

Switch to Brutalist in Storybook and check Button stories:
- Buttons should be pill-shaped (0px radius from --radius-rounded)
- If layers aren't working, buttons will keep their default radius
@rubyycheung
rubyycheung force-pushed the navi/feat/meta-theme branch from 2cf1509 to af47c28 Compare March 22, 2026 00:57
@rubyycheung

rubyycheung commented Mar 22, 2026

Copy link
Copy Markdown
Contributor Author

Updated analysis — after #802 / #803 / #804

This reflects the latest state of the PR after adopting @cixzhang's unified typography config (#804), variants-carry-styles (#803), and additional visual changes (floating label input, banner/dialog/switch refinements).

Current PR state

Area Files Insertions Deletions
Core (packages/core/) 9 +364 -74
Theme (packages/themes/meta/) 6 +644 — (new)
CLI (packages/cli/) 1 +33 -6

What the theme handles purely through defineTheme (no core changes)

Concern Mechanism
85 color token overrides tokens
Typography (fonts, scale, weights) typography config (#804)
Motion timing motion (#802)
Heading 1 → 28px, link color = accent Token overrides
Button pill shape + core variant color overrides components.button
5 custom button variants (muted/outline) variants.button with inline styles (#803)
Card/Section 20px padding, 32px radius Pre-existing --layout-padding-*, --card-radius vars
Dialog 32px radius + bg Pre-existing --dialog-radius var + class targeting
Banner card bg + 32px radius + primary icon --banner-status-bg, --banner-radius + banner-icon class target
Radio dot, slider track, calendar selected xdsClassName sub-element targets (#763)
Popover/dropdown/menu/tooltip 16px radius Pre-existing --popover-radius, --dropdown-radius + class targeting
Dialog: no header/footer borders, no body padding, large footer buttons layout-header, layout-content, layout-footer class targeting
Empty state heading 1, field status plain text, search input pill xdsClassName class targeting
Badge pastels, checkbox 4px radius, switch thumb + shadow Class targeting

16 xdsClassName targets used: banner-icon, radio-dot, slider-track, calendar-day, switch-thumb, emptystate-title, field-status, layout-header, layout-content, layout-footer, dropdown-menu, more-menu, popover, tooltip, text-input, checkbox

Core changes still required (9 files)

New features (2 files, +271/-31 lines):

File Change Lines
XDSTextInput.tsx labelPlacement="inset" floating label +166/-31
XDSButton.tsx 5 new variant StyleX objects +105

CSS custom properties for theming (4 files, 12 new vars):

Component Vars Reason
Radio (4) --xds-radio-border-width, --xds-radio-unchecked-border, --xds-radio-checked-border, --xds-radio-checked-bg Used inside color-mix() hover
Checkbox (2) --xds-checkbox-unchecked-border, --xds-checkbox-border-width Used inside color-mix() hover
Switch (5) --xds-switch-off-bg, --switch-thumb-size-off/on, --switch-thumb-off-x/on-x Hover color-mix() + sizing
Banner (1) --banner-status-bg StyleX specificity

Bug fixes + minor (3 files, +25/-7 lines):

  • Radio dot centering fix, radiusVars token usage
  • Slider thumb boxShadow
  • Banner hasIcon prop, <h3> title, per-status icon colors
  • EmptyState xdsClassName on title

Assessment

What worked:

  • Token-level theming (colors, radius, spacing, typography) requires zero core changes. The typography unification from #804 removed ~60 lines of manual config and 3 raw font token strings.
  • xdsClassName sub-element targeting from #763 replaced 10 CSS custom properties from a previous iteration of this PR. Direct property overrides on targets like banner-icon, radio-dot, slider-track work well.
  • The variants field from #803 cleanly handles custom button variant declaration + styling together, with generated TypeScript augmentation.

Concerns:

  1. 12 new CSS custom properties in core. 7 exist because color-mix() hover expressions reference them — xdsClassName can't override a value consumed inside a computed expression. The remaining 5 (banner-status-bg, switch thumb sizing) exist because StyleX's atomic class specificity beats the theme's @scope'd selectors. Each is individually reasonable, but the pattern of adding one-off CSS vars per theme need is worth watching. If every new theme requires similar escape hatches, the approach doesn't scale.

  2. Button variants in core (+105 lines). The 5 new variants are full StyleX objects with hover/focus/active states. The theme overrides colors via variants.button, but interaction states (:hover > @media (hover: hover) nesting) can't be replicated in plain CSS. For: these variants use universal tokens and work across all themes with sensible defaults. Against: they're currently Meta-specific; if other themes don't use them, they're 105 lines of unused StyleX definitions. The variants field was designed to let themes add variants without core changes, but it can't provide proper interaction states — this is a gap in the theming model.

  3. Floating label input (+166/-31 lines). The largest single change. It adds useState for focus tracking, animated transitions, status-colored labels, and a new layout mode to XDSTextInput. This is genuinely new functionality, not theming — any theme could use it. But it's a significant addition bundled into a theming PR. Could be its own PR.

  4. 8 core components modified for one theme. Even with the infrastructure, the first real theme exercise needed patches to Banner, Button, CheckboxInput, EmptyState, RadioList, Slider, Switch, and TextInput. This is expected for a first theme — future themes benefit from the escape hatches and xdsClassName targets added here. But it means the "theme-only, no core changes" promise isn't fully realized yet.

  5. xdsClassName targets added ad-hoc. emptystate-title was added specifically because the theme needed it. The CLI's KNOWN_COMPONENTS registry grew by 20+ entries. These additions were driven by one theme's needs rather than a systematic audit. A broader pass to add xdsClassName to all themeable sub-elements would prevent future themes from needing similar one-off core patches.

Bottom line: The theming infrastructure substantially reduced core changes compared to what a naive approach would require. But 9 core files, 12 CSS vars, and +364 lines for a single theme is still significant. The main question going forward: are the core changes made here universally useful (floating label, button variants, xdsClassName targets), or are they primarily serving the Meta theme? If the former, this PR is building shared infrastructure. If the latter, the theming model has room to grow.


Recommendations for reducing core changes in future themes

Three structural changes to the theming infrastructure that would eliminate most of the core modifications this PR needed.

1. Extract shared button interaction styles to base

Every button variant repeats identical hover/active/focus boilerplate — the backgroundImage overlay, outline ring, and outlineOffset are the same across all 9 variants. Only backgroundColor, color, border, and focus ring color differ.

If the shared interaction behavior moved into styles.base:

base: {
  backgroundImage: {
    ':hover': { '@media (hover: hover)': overlay },
    ':active': pressed,
  },
  outline: { ':focus-visible': '2px solid var(--button-focus-ring, var(--color-ring-focus))' },
}

Then each variant only sets static color properties + --button-focus-ring. The 5 custom Meta variants would drop from ~105 lines of core StyleX to ~15 lines in the theme. More importantly, defineTheme variants would work for hover/focus automatically because the base provides interaction behavior and the variant just provides colors.

2. Components own their base colors as CSS custom properties

The 7 color-mix() CSS vars (--xds-radio-*, --xds-checkbox-*, --xds-switch-*) exist because hover expressions reference them. But the var(--xds-*, fallback) escape hatch pattern is ad-hoc.

Instead, components could define the base color as part of their own StyleX styles:

radio: {
  '--radio-border': colorVars['--color-border-emphasized'],
  borderColor: {
    default: 'var(--radio-border)',
    ':hover': 'color-mix(in srgb, var(--radio-border), var(--color-hover-tint) 20%)',
  },
}

The theme overrides --radio-border via xdsClassName class targeting — no special --xds-* naming, no fallback wrapping. The component owns the var. Hover auto-derives. This is functionally the same mechanism but makes the custom property part of the component's CSS layer instead of a theming escape hatch.

This would apply to all components that use color-mix() for hover states (Radio, Checkbox, Switch), reducing the 7 escape-hatch vars to 0 while keeping the same behavior.

3. Support pseudo-selectors in defineTheme component overrides

XDSStyleOverrides is currently Record<string, string> — flat properties only. But CSS @scope fully supports pseudo-selectors:

@scope ([data-xds-theme="meta"]) {
  .xds-button.primary-muted:hover { background-image: linear-gradient(...); }
  .xds-button.primary-muted:focus-visible { outline: 2px solid ...; }
}

Extending the type to allow nested pseudo keys:

type XDSStyleOverrides = Record<string, string | Record<string, string>>;

// Usage in defineTheme:
variants: {
  button: {
    'primary-muted': {
      backgroundColor: 'light-dark(#ECF5FF, #182849)',
      ':hover': { backgroundImage: 'linear-gradient(...)' },
      ':focus-visible': { outline: '2px solid var(--color-ring-focus)' },
    },
  },
}

This would let themes define fully interactive variants without any core changes. Combined with recommendation #1, it could eliminate the need for button variant StyleX definitions in core entirely.

Combined impact

If all three were implemented:

Current After Reduction
12 CSS custom properties in core ~2 (banner-status-bg, switch sizing) -10 vars
105 lines of button variant StyleX 0 (theme handles variants + interactions) -105 lines
9 core files changed ~5 (TextInput floating label, Banner hasIcon/h3, EmptyState xdsClassName, Slider thumb shadow, Bug fixes) -4 files

The remaining core changes would be genuinely new features (floating label, hasIcon prop) and bug fixes — not theming escape hatches.

@rubyycheung

Copy link
Copy Markdown
Contributor Author

Updated analysis — CSS var cleanup + @layer specificity issue

Follow-up to the previous analysis. Two structural changes since then.

1. Eliminated all --xds-* escape hatches

Per the theming convention established in #763 and reinforced by the theme audit in #799: sub-element styling uses xdsClassName class targeting, not --xds-* CSS vars.

Components now own their CSS vars internally — they define the var with a default value in their StyleX styles and reference it in both default and hover states. Themes override the var via class targeting. Hover auto-derives because both states reference the same var.

Before (escape hatch) After (component-owned)
var(--xds-radio-unchecked-border, FALLBACK) Component: '--radio-border': TOKEN + var(--radio-border)
Theme sets --xds-radio-unchecked-border: #8F9296 Theme targets .xds-radio { --radio-border: #8F9296 }
Hover: color-mix(var(--xds-radio-unchecked-border, FALLBACK), ...) Hover: color-mix(var(--radio-border), ...) — auto-derives

7 component-owned vars remain (down from 12 --xds-* escape hatches):

Component Vars Purpose
Radio --radio-border-width, --radio-border, --radio-checked-border, --radio-checked-bg Border/bg with hover color-mix
Checkbox --checkbox-border-width, --checkbox-border Border with hover color-mix
Switch --switch-off-bg Track bg with hover color-mix

Zero --xds-* vars remain in core.

2. Discovered @layer specificity issue — requires !important

Problem: The theme CSS is generated inside @layer xds.theme. StyleX output is unlayered. Per the CSS cascade, unlayered styles beat layered styles for normal declarations. This means every theme property override that competes with a StyleX atomic class silently fails.

This affects:

  • padding on popover, dropdown-menu, more-menu (StyleX sets padding directly)
  • --container-padding and --layout-padding-* on card, section, dialog (StyleX sets these via container())
  • borderRadius, border-block-end-width, etc. on layout sub-elements

Current workaround: !important inside @layer. Per the CSS spec, !important declarations in a layer have higher priority than unlayered !important — the cascade order is reversed for important declarations. So @layer { .x { padding: 16px !important } } beats unlayered .y { padding: 12px }.

19 properties currently use !important in the Meta theme:

  • 12× --layout-padding-* / --container-padding (card, section, dialog)
  • padding (popover, dropdown, more-menu)
  • Plus border-width on layout-header/footer

This is a systemic issue, not specific to this theme. Any theme that overrides properties also set by StyleX will need !important. The root cause is that generateThemeCSS wraps output in @layer xds.theme, which was intended for cascade ordering but creates an unintended specificity disadvantage against StyleX's unlayered output.

Possible fixes:

  1. Remove @layer from theme CSS — theme rules would be unlayered like StyleX, competing on selector specificity alone (which @scope provides)
  2. stylex.defaults() API (stylex#1539) — would let StyleX values participate in the theme cascade properly
  3. Document that theme overrides need !important — pragmatic but not ideal

Current PR state (updated)

Area Files Insertions Deletions
Core 9 +371 -74
Theme 6 +644 — (new)
CLI 1 +33 -6

Core CSS vars: 7 component-owned (down from 12 --xds-* escape hatches)
Theme !important usage: 19 properties (due to @layer specificity)
--xds-* escape hatches: 0 (all eliminated)

cixzhang pushed a commit that referenced this pull request Mar 22, 2026
Extends XDSStyleOverrides to accept pseudo-class keys (e.g. ':hover',
':focus-visible', ':active') mapping to nested property objects. This
lets themes override interaction states directly via defineTheme without
CSS custom property escape hatches.

Before: themes needed per-component CSS vars like --xds-radio-unchecked-border
to indirectly swap values inside color-mix() hover expressions.

After: themes can override hover/focus/active states directly:

  components: {
    radio: {
      base: {
        borderColor: '#8F9296',
        ':hover': { borderColor: 'color-mix(in srgb, #8F9296, black 20%)' },
      },
    },
  }

Changes:
- XDSStyleOverrides type: Record<string, string | Record<string, string>>
- generateThemeRules (runtime): separates pseudo entries, emits as
  .xds-component:pseudo { ... } rules
- build-theme.mjs (CLI): same logic, pseudo rules skip prose co-selection
- 4 new test cases covering base+pseudo, variant+pseudo, pseudo-only,
  and no-pseudo regression

Ref: PR #692 identified 7 CSS vars needed solely because color-mix()
hover expressions couldn't be overridden via class targeting.
@cixzhang

Copy link
Copy Markdown
Contributor

Response to updated analysis — reducing core changes

Thanks for the thorough breakdown @rubyycheung. Here's how we're addressing the remaining core footprint.

Banner status var → eliminated

Banner's --banner-status-bg existed because status wasn't in the root xdsClassName call — themes couldn't target per-status backgrounds. Fixed in #810: xdsClassName('banner', {variant, status}). The Meta theme can now override per-status backgrounds directly via defineTheme component overrides.

-1 CSS var. Also added this pattern ("visual prop drives styling but isn't in xdsClassName") to the Theme Auditor checklist so it gets caught automatically.

7 color-mix() hover vars → pseudo-class overrides

The 7 vars (--xds-radio-*, --xds-checkbox-*, --xds-switch-off-bg) exist because color-mix() hover expressions bake in the base color. Rather than adding per-component CSS vars (slippery slope), #811 extends defineTheme to support pseudo-class selectors in component overrides:

components: {
  radio: {
    base: {
      borderColor: '#8F9296',
      ':hover': { borderColor: 'color-mix(in srgb, #8F9296, black 20%)' },
    },
  },
}

Themes own the full interaction behavior. No escape hatches in core. -7 CSS vars.

4 switch thumb vars → already handled

The switch thumb already has checked in its xdsClassName. In the dist build path, @scope overrides beat compiled StyleX — so the theme just targets .switch-thumb base and .switch-thumb.checked with the different dimensions. -4 CSS vars.

Net impact on this PR

Before After Delta
12 CSS custom properties in core 0 -12 vars
9 core files changed ~5 (TextInput floating label, Banner hasIcon/h3, EmptyState xdsClassName, Slider thumb shadow, bug fixes) -4 files

Remaining core changes are genuine new features and bug fixes — no theming escape hatches.

PRs to land before rebasing this branch

@cixzhang

Copy link
Copy Markdown
Contributor

Correction on my comment above — the override keys should be status:info, not bare info:

components: {
  banner: {
    'status:info': { backgroundColor: 'var(--color-card)' },
    'status:warning': { backgroundColor: 'var(--color-card)' },
    'status:error': { backgroundColor: 'var(--color-card)' },
    'status:success': { backgroundColor: 'var(--color-card)' },
  },
}

parseStyleKey expects prop:value format to generate the correct .info class selector.

- Add @xds/theme-meta package with updated token names matching main's naming convention
- Wire Meta theme into sandbox: providers, layout CSS, SandboxNav selector
- Use --xds-card-padding / --xds-section-padding instead of raw --container-padding vars
- Meta theme is sandbox-only (not in Storybook)
Updates based on Cindy's PR review comments:

- Use status:* keys for banner per-status overrides (#810)
- Use pseudo-class overrides for radio/checkbox/switch hover (#811)
  instead of component-owned CSS vars (--radio-border, etc.)
- Remove all duplicate token declarations
- Remove --banner-status-bg var, use status:* class targeting instead
- Remove --radio/checkbox/switch CSS vars not present on main
- Add fonts field for Figtree font loading via XDSTheme
- Add @xds/theme-meta to transpilePackages in next.config.mjs
- Add @xds/theme-meta to root build script
- Clean up component overrides to be more concise
Illustrations are consumed by the sandbox, not by the theme itself.
Keeps the theme package lean (tokens + icons + component overrides).
Sandbox imports them directly from src/components/MetaIllustrations.
…mponents

- fonts field doesn't exist on XDSDefineThemeInput; use url on typography
  roles instead (body.url) for font loading
- variants field was removed from defineTheme; declare custom variants as
  variant:name keys inside components.button instead
@rubyycheung
rubyycheung force-pushed the navi/feat/meta-theme branch from d2db868 to 40709a7 Compare March 27, 2026 04:01
@rubyycheung
rubyycheung merged commit 9eb8367 into main Mar 27, 2026
15 checks passed
@cixzhang
cixzhang deleted the navi/feat/meta-theme branch April 9, 2026 14:31
cixzhang pushed a commit that referenced this pull request Apr 26, 2026
* feat: add Meta Theme to sandbox (rebased on main)

- Add @xds/theme-meta package with updated token names matching main's naming convention
- Wire Meta theme into sandbox: providers, layout CSS, SandboxNav selector
- Use --xds-card-padding / --xds-section-padding instead of raw --container-padding vars
- Meta theme is sandbox-only (not in Storybook)

* refactor: align Meta theme with latest theming infrastructure

Updates based on Cindy's PR review comments:

- Use status:* keys for banner per-status overrides (#810)
- Use pseudo-class overrides for radio/checkbox/switch hover (#811)
  instead of component-owned CSS vars (--radio-border, etc.)
- Remove all duplicate token declarations
- Remove --banner-status-bg var, use status:* class targeting instead
- Remove --radio/checkbox/switch CSS vars not present on main
- Add fonts field for Figtree font loading via XDSTheme
- Add @xds/theme-meta to transpilePackages in next.config.mjs
- Add @xds/theme-meta to root build script
- Clean up component overrides to be more concise

* refactor: move illustrations from theme package to sandbox app

Illustrations are consumed by the sandbox, not by the theme itself.
Keeps the theme package lean (tokens + icons + component overrides).
Sandbox imports them directly from src/components/MetaIllustrations.

* fix: resolve build errors — remove fonts field, move variants into components

- fonts field doesn't exist on XDSDefineThemeInput; use url on typography
  roles instead (body.url) for font loading
- variants field was removed from defineTheme; declare custom variants as
  variant:name keys inside components.button instead
cixzhang pushed a commit that referenced this pull request Jun 21, 2026
* feat: add Meta Theme to sandbox (rebased on main)

- Add @xds/theme-meta package with updated token names matching main's naming convention
- Wire Meta theme into sandbox: providers, layout CSS, SandboxNav selector
- Use --xds-card-padding / --xds-section-padding instead of raw --container-padding vars
- Meta theme is sandbox-only (not in Storybook)

* refactor: align Meta theme with latest theming infrastructure

Updates based on Cindy's PR review comments:

- Use status:* keys for banner per-status overrides (#810)
- Use pseudo-class overrides for radio/checkbox/switch hover (#811)
  instead of component-owned CSS vars (--radio-border, etc.)
- Remove all duplicate token declarations
- Remove --banner-status-bg var, use status:* class targeting instead
- Remove --radio/checkbox/switch CSS vars not present on main
- Add fonts field for Figtree font loading via XDSTheme
- Add @xds/theme-meta to transpilePackages in next.config.mjs
- Add @xds/theme-meta to root build script
- Clean up component overrides to be more concise

* refactor: move illustrations from theme package to sandbox app

Illustrations are consumed by the sandbox, not by the theme itself.
Keeps the theme package lean (tokens + icons + component overrides).
Sandbox imports them directly from src/components/MetaIllustrations.

* fix: resolve build errors — remove fonts field, move variants into components

- fonts field doesn't exist on XDSDefineThemeInput; use url on typography
  roles instead (body.url) for font loading
- variants field was removed from defineTheme; declare custom variants as
  variant:name keys inside components.button instead
cixzhang pushed a commit that referenced this pull request Jun 21, 2026
* feat: add Meta Theme to sandbox (rebased on main)

- Add @xds/theme-meta package with updated token names matching main's naming convention
- Wire Meta theme into sandbox: providers, layout CSS, SandboxNav selector
- Use --xds-card-padding / --xds-section-padding instead of raw --container-padding vars
- Meta theme is sandbox-only (not in Storybook)

* refactor: align Meta theme with latest theming infrastructure

Updates based on Cindy's PR review comments:

- Use status:* keys for banner per-status overrides (#810)
- Use pseudo-class overrides for radio/checkbox/switch hover (#811)
  instead of component-owned CSS vars (--radio-border, etc.)
- Remove all duplicate token declarations
- Remove --banner-status-bg var, use status:* class targeting instead
- Remove --radio/checkbox/switch CSS vars not present on main
- Add fonts field for Figtree font loading via XDSTheme
- Add @xds/theme-meta to transpilePackages in next.config.mjs
- Add @xds/theme-meta to root build script
- Clean up component overrides to be more concise

* refactor: move illustrations from theme package to sandbox app

Illustrations are consumed by the sandbox, not by the theme itself.
Keeps the theme package lean (tokens + icons + component overrides).
Sandbox imports them directly from src/components/MetaIllustrations.

* fix: resolve build errors — remove fonts field, move variants into components

- fonts field doesn't exist on XDSDefineThemeInput; use url on typography
  roles instead (body.url) for font loading
- variants field was removed from defineTheme; declare custom variants as
  variant:name keys inside components.button instead
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants