---
title: Customization
description: "Theming with tokens, dark mode, per-instance overrides, and editing component source."
sidebar:
  order: 2
---

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:

```tsx
// 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`:

```tsx
// 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:

```tsx
'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](/docs/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:

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

Add your own the same way:

```tsx
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:

```tsx
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:

```tsx
// 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:

```tsx
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:

```tsx
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 |
