# Theming

Components read CSS variables. Change tokens for a system-wide look, or override a single component with classes and selectors.

## Where tokens live

Package installs use `@mielui/svelte/ui.css`. CLI installs use `src/lib/mielui/ui.css`. Both define the same color, typography, radius, and motion tokens.

## Override tokens

Set values in your app CSS after importing Mielui's sheet. Light defaults go in `@theme`. Dark values go under `.dark`.

~~~~css
@theme {
  --color-primary: #155eef;
  --color-background: #fcfcfd;
  --color-foreground: #101828;
  --radius-control: 9999px;
  --font-sans: 'DM Sans', sans-serif;
}

.dark {
  --color-background: #0d1118;
  --color-foreground: #f5f7fb;
  --color-primary: #7aa2ff;
}
~~~~

The focus ring, the info status color, and the accent tint are mixed from `--color-primary`, so one value rebrands all of them. Set `--color-on-primary` when the text on filled buttons needs a different color.

## Useful public tokens

| Group | Tokens |
| --- | --- |
| Color | `--color-background`, `--color-card`, `--color-panel`, `--color-secondary`, `--color-foreground`, `--color-foreground-muted`, `--color-primary`, `--color-on-primary`, `--color-button-foreground`, `--color-border`, `--color-input`, `--color-ring` |
| Status text | `--mielui-success-text`, `--mielui-warning-text`, `--mielui-error-text`, `--mielui-info-text` |
| Controls | `--size-control-sm`, `--size-control-md`, `--size-control-lg`, `--size-icon-md`, `--mielui-control-border`, `--focus-ring` |
| Type | `--font-sans`, `--font-mono`, `--font-header`, `--font-size-header`, `--font-weight-body`, `--font-weight-label`, `--font-weight-button` |
| Radius and density | `--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-xl`, `--radius-2xl`, `--radius-control`, `--mielui-space-unit` |
| Motion | `--motion-duration-hover`, `--motion-duration-menu`, `--motion-duration-panel`, `--motion-duration-sheet` |
| Elevation | `--elevation-1`, `--elevation-float`, `--elevation-control`, `--elevation-control-edge`, `--elevation-modal` |

Controls use three heights. Icon buttons match the medium height. `--focus-ring` is a 3px ring at half the primary color, and it composes with each control's existing edge. The default radius scale is 8, 10, 14, and 18 pixels from small to extra large. Plates use `--radius-2xl`, 26 pixels, and everything pressable uses `--radius-control`: 4 pixels in the sharp scale, 12 in the default scale, and a pill in the rounded scale.

Use the status text tokens for success, warning, error, and info copy on tinted backgrounds and cards. They reach 4.5:1 contrast, while the raw status colors remain for fills, icons, and charts.

## Dark mode

Toggle a `.dark` class on `<html>`. Components do not manage the class for you.

## Built-in presets

Mielui ships 18 built-in presets: `default`, `magic`, `bitsy`, `open`, `functional`, `daydream`, `honey`, `forest`, `ink`, `ocean`, `console`, `ember`, `lilac`, `profitable`, `raven`, `clawd`, `inspiration`, and `government`. Preview them on the [themes page](/themes), where you can copy each preset's CSS or JSON.

With the CLI, install a preset into `theme.css`:

~~~~sh
pnpm dlx @mielui/svelte add theme open
~~~~

Import it after `ui.css` to apply its overrides:

~~~~css
@import './lib/mielui/ui.css';
@import './lib/mielui/theme.css';
~~~~

`mielui list` shows available built-in theme slugs.

## Theme Studio

[Theme Studio](/studio) lets you start from a preset and adjust colors, fonts, spacing, motion, and surface effects. Open All color tokens at the end of the Color tab to edit individual color tokens.

Choose Export theme, open the CLI tab, download `mielui-theme.json` into your project root, and run the command for a new or existing Mielui setup. The JSON includes both color modes and all Studio overrides. New setups get `styles.css`, which imports `ui.css` followed by `theme.css`. Load that stylesheet in your root layout. Fonts must also be loaded by your app.

~~~~sh
pnpm dlx @mielui/svelte init --preset ./mielui-theme.json
~~~~

For an existing setup, run the command below. It replaces `theme.css`. Built-in preset slugs, such as `default`, can be used in place of the JSON path.

~~~~sh
pnpm dlx @mielui/svelte add theme ./mielui-theme.json
~~~~

## Theme JSON

Theme JSON version 4 is the format shared by Studio and the CLI. Export it from Studio to preserve both color modes and your overrides.

- Base fields: `version`, `slug`, `name`, `description`, `brand`, `neutral`, `radius`, `density`, `motion`, `fontSans`, `fontMono`, and `fontHeader` are required. `publisher` is optional.
- foundation: `light` and `dark` hold each mode's base, border, background, secondary, foreground, foregroundMuted, onPrimary, and buttonForeground colors.
- typography: headerSize, headerWeight, and roleWeights for body, label, button, badge, and description text.
- tokens: `shared`, `light`, and `dark` hold raw token overrides keyed by custom property, including per-mode values for `--color-primary`.
- chrome: borders, edgeHighlight, surfaceShadows, controlShadows, dialogShadows, travelingHighlight, primaryStroke, and interactiveCursor. Turning off travelingHighlight keeps the selected fill and removes its movement.

Set `motion: "none"` to disable animations, including dialogs, menus, and the traveling highlight. Edge highlight strength ranges from 0 to 1.

## Global glass surfaces

Surfaces are solid by default. Set `--mielui-surface: glass` on `:root` and components with surface support inherit that choice when the prop is omitted. Set `surface="solid"` or `surface="glass"` on one component to override the theme. Put the variable on `:root` so portaled menus and dialogs inherit it too.

~~~~css
:root {
  --mielui-surface: glass;
}
~~~~

The global setting uses CSS style queries. Browsers without style-query support retain solid surfaces. Explicit `surface="glass"` still works with backdrop-filter support. Reduced transparency keeps an opaque background and removes blur.

## Class overrides

Styled components accept `class`. Use Tailwind utilities or your own classes for one-off tweaks. Your classes win over the component defaults.

~~~~svelte
<Button class="w-full rounded-2xl">Continue</Button>
~~~~

## Component selectors

Components render `data-ui`, and often `data-variant` or `data-size`. Scope CSS to a family without forking files.

~~~~css
[data-ui='button'][data-variant='primary'] {
  border-radius: var(--radius-md);
}

[data-ui='badge'][data-variant='secondary'] {
  text-transform: uppercase;
}
~~~~

## Edit the source

With the CLI path, files live under `src/lib/mielui/components/<name>/`. Edit them when you need behavior changes, not just style.

~~~~console
# after: pnpm dlx @mielui/svelte add button
src/lib/mielui/components/button/
├── button.svelte
└── index.ts
~~~~

## Borders

Framed surfaces are a white frame holding a recessed inset in the page background. Set `chrome.borders` to `"single"` so the inset meets the border of the frame, or `"double"` for a gutter between the two. The default is `"single"`.

~~~~ts
const theme = {
    ...DEFAULT_THEME,
    chrome: { borders: 'single' as const }
};

const css = themeToCss(theme);
~~~~

The setting covers inset layouts: dialogs, sheets, drawers, toasts, Notch, code blocks, diffs, inset tables, inset and panel cards, alerts, and composers. Menus, selects, comboboxes, popovers, hover cards, date-picker panels, and chart tooltips always use a single border.

## Chart colors

`--chart-1` through `--chart-5` color data series in order. Cartesian charts, pie charts, gauges, and heatmaps read them. Explicit series colors and semantic gauge tones still take precedence.

~~~~css
:root {
  --chart-1: #b8a1f2;
  --chart-2: #f49d9d;
  --chart-3: #8bc7f5;
  --chart-4: #8ed8b0;
  --chart-5: #f2d77d;
}
~~~~

## Inset strip position

`--mielui-inset-position` moves exposed chrome above or below its inset content. It takes `top` or `bottom`. Without that token, components keep their authored order. Override it locally with `class="[--mielui-inset-position:top]"` when a header must stay above its content. Data tables keep filters above rows and pagination below them.

## Edge highlights

Filled controls are flat by default: buttons, moving thumbs, and selected segments take one solid fill held by a hairline, with no gradient or inner light. Set `chrome.edgeHighlight` above 0 to light them all at once. It also scales the light edge on keycaps. The default is 0, the flat look; 0.33 is the original lit look and 1 is full strength. Text fields, selection triggers, and outline buttons carry no fill either way.

The `glow` button variant stays lit in a flat theme. To light another control on its own, add the `mielui-glow-lit` class next to `mielui-glow`. A near-white primary button with dark text is a theme choice: apply the built-in Open preset, whose dark primary is near-white, or set the brand color.

~~~~ts
const theme = {
    ...DEFAULT_THEME,
    chrome: { edgeHighlight: 0.5 }
};

const css = themeToCss(theme);
~~~~

For the rendered guide, visit [/docs/theming](/docs/theming).
