StyleX
@stylexjs/stylex needs no alias configuration — the extractor recognises the package directly, through the default import, a namespace import, or named imports.
Styles and props
create() becomes a namespace-to-class-name object and props() becomes the attributes to spread:
1import stylex from '@stylexjs/stylex' 2 3const styles = stylex.create({ 4 base: { display: 'inline-block' }, 5}) 6const colors = stylex.create({ red: { color: 'red' }, blue: { color: 'blue' } }) 7 8const el = <div {...stylex.props(styles.base, colors[color])} />1import stylex from '@stylexjs/stylex' 2 3const styles = stylex.create({ 4 base: { display: 'inline-block' }, 5}) 6const colors = stylex.create({ red: { color: 'red' }, blue: { color: 'blue' } }) 7 8const el = <div {...stylex.props(styles.base, colors[color])} />
1const styles = { base: 'a' } 2const colors = { red: 'b', blue: 'c' } 3 4const el = <div {...{ className: `a ${colors[color] || ''}` }} />1const styles = { base: 'a' } 2const colors = { red: 'b', blue: 'c' } 3 4const el = <div {...{ className: `a ${colors[color] || ''}` }} />
Computed access such as colors[color] is resolved against the object the create() call was rewritten into, so every generated atom stays reachable. stylex.attrs() behaves the same but emits class instead of className.
Numbers get the unit StyleX gives them: fontSize: 16 is 16px, transitionDuration: 300 is 300ms, and unitless properties such as lineHeight stay as written. A number passed to a dynamic style gets the same unit at runtime.
Accepted argument shapes:
1stylex.props(styles.base) // member access 2stylex.props(styles['base']) // literal key 3stylex.props(colors[color]) // runtime key 4stylex.props([styles.a, styles.b]) // StyleXArray, nestable 5stylex.props(on && styles.active) // conditional 6stylex.props(styles?.base) // optional chaining1stylex.props(styles.base) // member access 2stylex.props(styles['base']) // literal key 3stylex.props(colors[color]) // runtime key 4stylex.props([styles.a, styles.b]) // StyleXArray, nestable 5stylex.props(on && styles.active) // conditional 6stylex.props(styles?.base) // optional chaining
Variables and themes
1const colors = stylex.defineVars({ primary: 'blue', secondary: 'grey' }) 2const dark = stylex.createTheme(colors, { primary: 'navy' }) 3const styles = stylex.create({ box: { color: colors.primary } }) 4 5const el = <div {...stylex.props(dark, styles.box)} />1const colors = stylex.defineVars({ primary: 'blue', secondary: 'grey' }) 2const dark = stylex.createTheme(colors, { primary: 'navy' }) 3const styles = stylex.create({ box: { color: colors.primary } }) 4 5const el = <div {...stylex.props(dark, styles.box)} />
1:root { 2 --a: blue; 3 --b: grey; 4} 5.c { 6 --a: navy; 7} 8.d { 9 color: var(--a); 10}1:root { 2 --a: blue; 3 --b: grey; 4} 5.c { 6 --a: navy; 7} 8.d { 9 color: var(--a); 10}
1const colors = { primary: 'var(--a)', secondary: 'var(--b)' } 2const dark = 'c' 3const styles = { box: 'd' } 4const el = <div {...{ className: 'c d' }} />1const colors = { primary: 'var(--a)', secondary: 'var(--b)' } 2const dark = 'c' 3const styles = { box: 'd' } 4const el = <div {...{ className: 'c d' }} />
createThemeContract() produces the same references without publishing a :root block, and defineConsts() inlines its values into the create() calls that read them.
A variable or theme value can depend on at-rules, with an optional types.*() wrapper. Each set of conditions becomes one block after the unconditional values, and nested conditions come after the ones they refine, so the more specific value wins:
1const DARK = '@media (prefers-color-scheme: dark)' 2 3const colors = stylex.defineVars({ 4 text: { default: 'black', [DARK]: 'white' }, 5 accent: stylex.types.color({ default: 'blue', [DARK]: 'lightblue' }), 6}) 7const dracula = stylex.createTheme(colors, { 8 text: { default: 'purple', [DARK]: 'pink' }, 9})1const DARK = '@media (prefers-color-scheme: dark)' 2 3const colors = stylex.defineVars({ 4 text: { default: 'black', [DARK]: 'white' }, 5 accent: stylex.types.color({ default: 'blue', [DARK]: 'lightblue' }), 6}) 7const dracula = stylex.createTheme(colors, { 8 text: { default: 'purple', [DARK]: 'pink' }, 9})
1:root { 2 --a: black; 3 --b: blue; 4} 5@media (prefers-color-scheme: dark) { 6 :root { 7 --a: white; 8 --b: lightblue; 9 } 10} 11.c { 12 --a: purple; 13} 14@media (prefers-color-scheme: dark) { 15 .c { 16 --a: pink; 17 } 18}1:root { 2 --a: black; 3 --b: blue; 4} 5@media (prefers-color-scheme: dark) { 6 :root { 7 --a: white; 8 --b: lightblue; 9 } 10} 11.c { 12 --a: purple; 13} 14@media (prefers-color-scheme: dark) { 15 .c { 16 --a: pink; 17 } 18}
Conditions are default, @media, @supports and @container keys, and null leaves a variable unset under its condition. A value that is not static is a build error naming it; pseudo-class keys count as not static, because a variable is set on :root or a theme class, not on the element being styled.
As StyleX requires, variables usually live in a .stylex.ts file and are imported where they are used. Imports resolve like the bundler resolves them, and the importing file reads the same custom properties, theme classes and constants that the defining file generates:
1// tokens.stylex.ts 2export const colors = stylex.defineVars({ primary: 'blue' }) 3export const spacing = stylex.defineConsts({ gap: '8px' }) 4 5// Button.tsx 6import { colors, spacing } from './tokens.stylex' 7 8const dark = stylex.createTheme(colors, { primary: 'navy' }) 9const styles = stylex.create({ 10 box: { color: colors.primary, marginTop: spacing.gap }, 11})1// tokens.stylex.ts 2export const colors = stylex.defineVars({ primary: 'blue' }) 3export const spacing = stylex.defineConsts({ gap: '8px' }) 4 5// Button.tsx 6import { colors, spacing } from './tokens.stylex' 7 8const dark = stylex.createTheme(colors, { primary: 'navy' }) 9const styles = stylex.create({ 10 box: { color: colors.primary, marginTop: spacing.gap }, 11})
A value in create() that this file's own functions compute from constants with exact built-ins, such as double(SIZE), is computed at build time; another module's code never runs, and code whose result is not certain (Date, Math.random, Math.sin, locale methods, new, this...) is not run. A value only known at runtime, such as a prop or a state, is a build error naming it, as is anything create(), defineVars(), defineConsts(), createTheme(), keyframes(), positionTry() or viewTransitionClass() cannot read (a spread, a computed key, a non-object argument). Pass runtime values through a dynamic style ((color) => ({ color })), which becomes a CSS variable instead.
stylex.props() accepts styles compiled elsewhere — a style prop a parent passes, a spread, another module's namespaces — and joins their class names at runtime; namespaces of this file resolve at build time.
At-rules
1const fallback = stylex.positionTry({ top: '0', insetBlockEnd: 'auto' }) 2const transition = stylex.viewTransitionClass({ animationDuration: '300ms' })1const fallback = stylex.positionTry({ top: '0', insetBlockEnd: 'auto' }) 2const transition = stylex.viewTransitionClass({ animationDuration: '300ms' })
1@position-try --a { 2 top: 0; 3 inset-block-end: auto; 4} 5.b { 6 animation-duration: 300ms; 7}1@position-try --a { 2 top: 0; 3 inset-block-end: auto; 4} 5.b { 6 animation-duration: 300ms; 7}
Value helpers
firstThatWorks() emits its fallbacks in CSS order, types.*() unwraps to the inner value, and include() merges another namespace's classes. Values may be plain literals, condition objects ({ default, ':hover', '@media ...' }), or arrow functions, which become CSS variables bound at the call site.