Skip to content
Esc
navigateopen⌘Jpreview
On this page

Customization

Theming with tokens, dark mode, per-instance overrides, and editing component source.

Components are copied into your project — you own the source. Customization happens at four levels, from broadest to most targeted:

  1. Tokens — retheme the whole app by editing one file.
  2. Themes — swap token values for a subtree or the whole page (dark mode).
  3. The style prop — override any component instance from the outside.
  4. The source itself — change a component’s anatomy or add variants.

Tokens

lib/tokens.stylex.ts is the single source of truth for colors, radii, fonts, and shadows. Every component reads from it — change a value and the whole system follows:

// lib/tokens.stylex.ts
export const colors = stylex.defineVars({
  primary: 'oklch(0.55 0.2 260)',   // ← your brand color here
  primaryForeground: 'oklch(0.985 0 0)',
  // ...
});

export const radius = stylex.defineVars({
  md: '0.5rem',              // ← sharper or rounder corners everywhere
  // ...
});

Semantic names use the familiar convention (background, foreground, primary, muted, accent, destructive, border, ring, …), so the mental model transfers directly.

Colors are oklch() — perceptually uniform, so shifting lightness or chroma behaves predictably, and any CSS color (hex, hsl space syntax) works too. A few value-format rules keep files installable through the shadcn CLI (its transformer rewrites some CSS-like strings): never use comma syntax like rgba(0, 0, 0, 0.5); write black-with-alpha as oklch(0% 0 0deg / 50%) (adjacent equal zeros get collapsed); keep shadow colors hex-alpha.

Themes and dark mode

A theme overrides token values for a DOM subtree. lib/themes.ts ships a darkTheme built with stylex.createTheme:

// lib/themes.ts
export const darkTheme = stylex.createTheme(colors, {
  background: 'oklch(0.145 0 0)',
  foreground: 'oklch(0.985 0 0)',
  // ...
});

Apply it to <html> — not a wrapper <div> — because dialogs, popovers, menus, and toasts portal to <body> and would escape a subtree theme:

'use client';

useEffect(() => {
  const { className } = stylex.props(dark && darkTheme);
  document.documentElement.className = className ?? '';
}, [dark]);

Brand themes work the same way: create as many as you need with stylex.createTheme(colors, { ... }) and apply per page, per tenant, or per section. Themes can be nested — the closest one wins. See Dark mode for the no-flash setup.

Accent themes compose

A theme doesn’t have to override every token. lib/themes.ts also ships partial accent themes (violetTheme, emeraldTheme) that swap only the brand tokens (primary, primaryForeground, ring). Partial themes compose with full ones — later themes win per token:

// Dark mode with a violet brand color:
const { className } = stylex.props(darkTheme, violetTheme);
document.documentElement.className = className ?? '';

Add your own the same way:

export const brandTheme = stylex.createTheme(colors, {
  primary: 'oklch(0.55 0.2 260)',
  primaryForeground: 'oklch(0.985 0 0)',
  ring: 'oklch(0.55 0.2 260)',
});

One thing themes are not: runtime-generated. StyleX resolves every theme at compile time (that’s what keeps the CSS static and small), so a “pick-a-color” theme builder must emit a createTheme call per choice rather than compute values in the browser.

The style prop

Every styled component (and part) accepts style?: StyleXStyles. It is merged last, so your overrides always win — StyleX resolves conflicts deterministically at compile time. No tailwind-merge, no !important, no specificity wars:

const styles = stylex.create({
  pill: { borderRadius: '9999px' },
  wide: { paddingInline: '3rem' },
});

<Button style={styles.pill}>Pill</Button>
<Button style={[styles.pill, styles.wide]}>Pill + wide</Button>  // arrays compose

This is the right tool for one-off tweaks: a wider dialog, a full-width button, an extra margin. If you find yourself repeating the same override, promote it to a variant instead.

Editing the source

Components live in components/ui/ in your project. They are yours — edit them. The most common edit is adding a variant:

// components/ui/button.tsx
export type ButtonVariant =
  | 'primary'
  | 'secondary'
  | 'outline'
  | 'ghost'
  | 'destructive'
  | 'success';           // 1. extend the type

const variants = stylex.create({
  // ...existing variants...
  success: {              // 2. add the styles
    backgroundColor: 'oklch(0.63 0.17 149)',
    color: colors.primaryForeground,
  },
});

// 3. done: <Button variant="success">Save</Button>

Sizes work the same way through the sizes map.

Styling Base UI state

Base UI mirrors every part’s state as a data attribute (data-checked, data-highlighted, data-popup-open, data-starting-style, …), and StyleX (0.18+) accepts attribute selectors as condition keys — so state styling is declared inline, the Tailwind data-[state=open]: equivalent:

const styles = stylex.create({
  root: {
    backgroundColor: {
      default: colors.input,
      '[data-checked]': colors.primary,   // like data-[state=checked]:bg-primary
    },
  },
});

<Switch.Root {...stylex.props(styles.root, style)} />

Compound states nest ('[data-side="bottom"]': { '[data-ending-style]': … }) or combine into one key ('[data-side="bottom"][data-ending-style]'). One gotcha: a conditional custom property ('--x': { … }) must use default: null and a var(--x, fallback) where it’s read — a non-null default is emitted outside the CSS layer and would always win.

Interaction states that CSS can express directly (hover, focus, active) use the same conditional-value syntax:

backgroundColor: {
  default: colors.background,
  ':hover': colors.accent,
},
outline: {
  default: 'none',
  ':focus-visible': `2px solid ${colors.ring}`,
},

Where to make which change

You want to… Do this
Change the brand color / radius / font everywhere Edit lib/tokens.stylex.ts
Add dark mode or a second brand look stylex.createTheme in lib/themes.ts, apply to <html>
Tweak one instance style prop
Reusable new look for a component Add a variant in its source file
Change a component’s structure or behavior Edit components/ui/<name>.tsx — you own it

Was this page helpful?