> For AI agents: the complete documentation index is available at https://mfeflow.github.io/mfeflow/llms.txt, the full documentation bundle is available at https://mfeflow.github.io/mfeflow/llms-full.txt.

# theming

This page covers how to give your platform light and dark themes that all remotes follow, without each remote carrying theme code. The shell owns the choice, and remotes only read CSS variables.

## What you get

The shell gets a provider, a theme picker, and a small script that applies the saved theme before the page paints. Remotes get a `tokens` object that points at the same variables. This feature is off by default.

## Add it

```sh
pnpm mfeflow add theming
```

What changed in the shell and each remote:

```text
apps/shell/src/theme/
├── themes.ts          # your light and dark tokens
├── Theme.tsx          # the provider that wraps the app
├── ThemeToggle.tsx    # the switch in the header
├── theme.css          # base variables
├── apply-stored.ts    # applies the saved theme at boot
└── head-script.ts     # the no-flash script for the head

apps/<remote>/src/theme/
└── tokens.ts          # CSS variable references for the remote
```

Three existing shell files are edited:

```text
App.tsx            # wrapped in Theme
bootstrap.tsx      # gets a boot step
rsbuild.config.ts  # gets an import
```

If [testing](https://mfeflow.github.io/mfeflow/features/testing.md) is installed, the stored theme, the toggle and the tokens get tests.

## Use it

Define the tokens once in the shell. Each token becomes a `--mf-<token>` variable on the page's root element.

```ts title="apps/shell/src/theme/themes.ts"
export const themes = [
  defineTheme('light', {
    'color-bg': '#ffffff',
    'color-fg': '#111827',
    'color-primary': '#2563eb',
  }),
  defineTheme(
    'dark',
    { 'color-bg': '#0b1020', 'color-fg': '#e5e7eb', 'color-primary': '#60a5fa' },
    { colorScheme: 'dark' },
  ),
];
```

A remote then uses them in CSS:

```css
.card {
  background: var(--mf-color-surface);
  color: var(--mf-color-fg);
}
```

Or in TypeScript, with fallbacks for when the remote runs on its own:

```ts title="apps/cart/src/theme/tokens.ts"
export const tokens = {
  bg: cssVar('color-bg', '#ffffff'),
  fg: cssVar('color-fg', '#111827'),
  surface: cssVar('color-surface', '#f3f4f6'),
  primary: cssVar('color-primary', '#2563eb'),
};
```

Switching themes rewrites the variables on the root element, so every remote changes at once with no re-render and no event passed between them.

## Configure it

There are no install options. Everything is in `themes.ts`, which is yours to edit:

| Setting        | Default         | What it does                                                           |
| -------------- | --------------- | ---------------------------------------------------------------------- |
| `themes`       | `light`, `dark` | The list of themes and their tokens; add tokens or themes freely       |
| `defaultTheme` | `light`         | The theme used when nothing is stored and the system does not say dark |
| `followSystem` | `true`          | Use the system dark-mode setting when no choice is stored              |

## How it works

The choice is saved in the shell's storage under `shell:theme`. Two things apply it on load. A script generated by `noFlashScript` sits first in the shell's head, so the stored colours are on the page before any bundle loads. Then `apply-stored.ts` runs as the first boot step and does the same before the first component renders.

Both use the same order: a stored name that matches one of your themes, then the system dark preference if `followSystem` is on, then `defaultTheme`.

## Gotchas

- If your Content Security Policy blocks inline scripts, the head script is skipped. The boot step still applies the theme before the first render, but the page shows default colours until JavaScript has run.
- A stored value that is not one of your themes, such as `constructor`, is ignored.
- Token and theme names are lowercase letters, digits and dashes. Anything else throws when the theme is defined.

## Reference

- [tailwind](https://mfeflow.github.io/mfeflow/features/tailwind.md), which maps its colours to the `--mf-color-*` tokens
- [ui-shadcn](https://mfeflow.github.io/mfeflow/features/ui-shadcn.md), whose components are styled with Tailwind
- [Shared singletons](https://mfeflow.github.io/mfeflow/concepts/shared-singletons.md)
