RFC: stylex.defaults() - lower-specificity styles for predictable external overrides
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 10.3k
- Forks
- 484
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 13
Description
Describe the feature request
Problem
When multiple stylex.create() results are merged via stylex.props(), the JS-side merge is correct - styleq processes arguments right-to-left and the last argument wins per property. However, the CSS output is flat: all atomic classes have the same specificity (0,1,0).
This works for StyleX-to-StyleX merging because styleq removes losing classes from the DOM. But it creates a problem when external CSS needs to interact with StyleX output at predictable specificity levels.
Concrete example
A design system ships components built with StyleX. A theme system needs to override default visual styles (background, border-radius, font-weight) while preserving variant-specific styles that the component applies conditionally.
const defaults = stylex.create({
root: { backgroundColor: "transparent", fontWeight: "400" },
});
const variants = stylex.create({
primary: { backgroundColor: "var(--color-accent)" },
});
<Button variant="primary" xstyle={overrides} />
// Internally: stylex.props(defaults.root, variants[variant], xstyle)
Two levels of styles:
- Defaults - base visual that should be easily overridable
- Variants - applied conditionally, should take priority over defaults
Both produce (0,1,0) selectors. External CSS cannot reliably override defaults without fighting variants.
Desired cascade behavior
(0,0,0) Component defaults - yield to everything
(0,1,0) External overrides - theme CSS, host app styles
(0,1,0) Component variants - source-order after external CSS
(0,1,0) Consumer xstyle - wins via styleq removing losing classes
Proposal: stylex.defaults()
Identical to stylex.create() but compiled CSS is wrapped in :where(), giving zero specificity (0,0,0).
API
const defaults = stylex.defaults({
root: {
backgroundColor: "transparent",
fontWeight: "400",
borderRadius: "var(--radius-2)",
},
});
const variants = stylex.create({
primary: { backgroundColor: "var(--color-accent)" },
});
function Button({ variant, xstyle }) {
return <button {...stylex.props(defaults.root, variants[variant], xstyle)} />;
}
Compiled output
:where(.x1abc) { background-color: transparent; }
:where(.x2def) { font-weight: 400; }
.x4jkl { background-color: var(--color-accent); }
Why this fits StyleX's model
- Still static, compiled at build time
- Still co-located, same file, same pattern
- Still atomic, same deduplication model
- Uses standard CSS
:where()- no custom cascade mechanisms - Opt-in, existing behavior unchanged
- Composable with
stylex.props()
Use cases beyond design systems
- Utility-first defaults that consumers override
- Reset/normalize styles at zero specificity
- Slot patterns: parent provides defaults child overrides
Implementation considerations
- Babel plugin: new entry point wrapping output in
:where() styleqruntime: no changes needed- Priority ordering still applies within
:where() - No deduplication across
defaults/create(different specificity = different rules)
Alternatives considered
| Approach | Why it falls short |
|---|---|
| CSS @layer | Can't split atomic classes across layers |
| Doubled selectors | Raises all styles, can't lower defaults |
| CSS vars for everything | Var explosion |
| !important in layers | Reverses layer order |
| Runtime injection ordering | Source order only breaks ties at equal specificity |
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the Babel plugin entry point and review how stylex.create() output is compiled into CSS. Confirm how a new defaults() entry point could wrap generated selectors in :where(), preserve existing create() behavior, and remain compatible with the styleq runtime and stylex.props() composition.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- css, javascript
- Domain
- frontend, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100