Define base vs theme styling boundary
- Dominant language
- TypeScript
- Stars
- 13k
- Forks
- 1.1k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 690
Description
## Problem
XDS components currently carry full visual styling in their base StyleX code — variant colors, hover/active overlays, focus ring colors, border treatments. This means:
1. **`xds.css` is heavier than it needs to be** — every variant's color, hover gradient, and focus style ships in the base CSS even when a theme overrides all of it
2. **Themes can only override, not define** — the base already has opinions, so themes fight specificity rather than being the source of truth
3. **Theme differentiation is muted** — switching themes mostly swaps token values but the structural styling (overlay patterns, focus ring approach, border treatment) stays identical because it's baked into the base
4. **Meta brand colors in base tokens** — the default `tokens.stylex.ts` values use Meta brand blue (`#0064E0`), which needs to change before open sourcing
## Proposal
Establish a clear boundary: **base styles define structure, themes define personality.**
### Base component styles (component source → `@layer xds-base`)
**Should include:**
- Layout — display, flex/grid, alignment, position, gap
- Sizing — padding, min-height (via tokens)
- Interactive behavior — cursor, pointer-events, transition property list + timing function
- Accessibility structure — focus-visible outline width/offset, disabled opacity
- Typography structure — font-family inherit, semantic type scale tokens
- Reset-adjacent — border: 0, appearance: none
**Should NOT include:**
- Variant color assignments (backgroundColor, color per variant)
- Hover/active overlay patterns (backgroundImage gradients)
- Focus ring colors
- Border-color on variants
- Box shadow values
- Transition duration values
### Theme styles (`defineTheme` → `@layer xds-theme`)
**Owns:**
- All color assignments per variant
- Hover/active interaction patterns
- Focus ring color + style
- Border radius scale
- Shadow definitions
- Motion/duration values
- Typography font families
- Component-specific visual overrides
## Tradeoffs
### Benefits of thinner base
- **Smaller `xds.css`** — less CSS to parse and apply. Every variant's color ramp, hover gradient, and focus style currently ships in base even when immediately overridden by a theme
- **Better performance** — fewer base rules means fewer specificity calculations for the browser, and theme `@scope` rules don't need to fight base styles
- **Themes become the demo** — switching between default/neutral/brutalist produces dramatically different results because the theme defines the entire visual experience, not just accent swaps
- **Cleaner specificity** — no `@layer xds-base` vs `@layer xds-theme` conflicts for visual properties. Base owns structure, theme owns appearance, no overlap
### Cost of thinner base
- **Can't rely on base variants for shared modules** — today you can build a module on top of XDS and count on `` having a blue background regardless of theme. With thinner base, that card is only blue if the active theme defines it. A module that depends on color variants without requiring a theme would get unstyled components
- **"No theme" rendering is minimal** — without a theme, components would have structure but no visual personality. This could be a feature (forces theme adoption) or a friction point (harder to get started)
- **Migration cost** — every component with variant colors in StyleX needs to move those definitions into the default theme's `defineTheme` config
## Current state: components with variant-level visual styling in base
These components have color/visual variant styles baked into their StyleX source that would need to move to themes:
### Full variant color blocks
| Component | What's in base today |
|-----------|----------------------|
| **Button** | 4 variants (primary/secondary/ghost/destructive) with backgroundColor, color, hover overlay gradients, focus outline color |
| **Badge** | 5 semantic + 10 color variants, each with backgroundColor + color |
| **Card** | 11 background color variants (default/transparent/muted + 8 named colors) |
| **Token** | 11 color variants (default + 10 named colors) with backgroundColor + color |
| **Icon** | 9 semantic + 10 named color variants |
| **StatusDot** | 5 status variants with backgroundColor |
| **Banner** | 4 status variants with backgroundColor |
| **FieldStatus** | 3 status variants with backgroundColor + color |
| **ProgressBar** | Fill color per variant (accent/success/warning) |
### Interactive visual styling in base
| Component | What's in base today |
|-----------|----------------------|
| **Switch** | Track + thumb colors, checked state colors, hover states |
| **CheckboxInput** | Border colors, checked background, hover states, focus outline color |
| **RadioList** | Border colors, checked state, dot color |
| **SelectableCard** | Selected border + shadow, hover overlay |
| **Slider** | Track + thumb colors, focus ring |
| **TextInput** | Focus outline color |
| **TabList** | Active indicator color, hover states |
| **SideNav** | Active item background, hover states |
## Open questions
1. **Where do semantic mappings live?** Variant-to-token mappings like "primary button uses `--color-accent`" are arguably structural (every theme should map primary→accent). Should these stay in base as thin token references, with only the *values* coming from themes? Or should even the mapping be theme-owned?
2. **Named color variants** — Badge, Card, Token, Icon all have `blue`, `red`, `green`, etc. variants that map directly to named color tokens. These are pure token references (`colorVars['--color-background-blue']`). Moving these to theme seems wrong — they're definitional. But keeping them in base means base still has visual opinions.
3. **"No theme" experience** — Should XDS render anything useful without a theme applied? Today it does (full visual output from base tokens). If we thin the base, the no-theme experience becomes structural-only. Is that acceptable?
4. **Migration path** — This is a breaking visual change for anyone using `@xds/core/xds.css` without a theme. How do we handle that? Options:
- Major version bump
- Transitional "base-compat" CSS that preserves current behavior
- Document it as a required migration step
## Related
- Theme default → neutral migration (open source prep)
- Base token color replacement (`tokens.stylex.ts` Meta brand → neutral)
Contributor guide
Assessment
This issue has not been assessed yet.