# Theme

## Usage

`Theme` is the theme provider. It applies a `defineTheme()` object to its subtree with CSS variables, and descendants read that theme with `useTheme()`. There is no separate provider component.

Token names are the upstream names (`--color-accent`) or this package's names (`--astryx-color-accent`). A `[light, dark]` tuple becomes `light-dark()`. `mode` is `system` by default, or `light` / `dark`. The outermost `Theme` also writes `data-astryx-theme` and `data-astryx-color-mode` on `document.documentElement`. Nested themes do not.

`syntax` on the theme object sets code colors for the subtree. A closer `SyntaxTheme` wins. Import the component from `@astryx-vue/core/Theme`.

```vue
<script setup lang="ts">
import { defineTheme, Theme } from '@astryx-vue/core/Theme'

const ocean = defineTheme({
  name: 'ocean',
  tokens: {
    '--color-accent': ['#0077B6', '#48CAE4'],
    '--color-background-surface': ['#F0F8FF', '#0A1628'],
    '--color-text-primary': ['#0A1317', '#FFFFFF'],
    '--radius-container': '16px',
  },
})
</script>

<template>
  <Theme :theme="ocean">
    <p>Ocean</p>
  </Theme>
</template>
```

`neutralTheme` is the empty theme. Its colors still come from `@astryx-vue/themes/neutral.css`. The seven shipped themes stay handwritten CSS. They are not rebuilt from the generators below.

## Generated from a few numbers

`color`, `typography`, `radius`, and `motion` fill tokens on the theme object. A matching `tokens` entry wins. A reference such as `var(--color-accent)` is rewritten to the `--astryx-` name.

`color.accent` is one hex color, or `[light, dark]`. The palette is HCT: hue and chroma come from the seed, tone walks a fixed ramp, and the stops are paired into light and dark. Omit the accent and set `neutralStyle` to recolor only the neutrals. `--color-on-accent` is baked from the seed so the contrast holds. The muted, text, and icon accent tokens reference `--color-accent`, so a later accent override carries them along.

Type sizes come from `typography.scale` `base` and `ratio`, rounded from `base × ratio` at each step. Radii come from `radius` `base` and `multiplier`. `none` and `full` stay fixed. Durations come from `motion` `fast`, `medium`, `slow`, and `ratio`. The min step multiplies the base by the ratio, and the max step divides by it. Both round to 5 milliseconds.

`components` overrides styles by theme target, written `<component-kebab>-<part>`, such as `button` or `card-header`. `base` hits the target itself. `variant:secondary` hits `[data-variant="secondary"]`. `Theme` puts the compiled rules in the `astryx.overrides` layer, scoped to its `data-astryx-theme` subtree and stopping at a nested theme. `localTokens`, `adaptations`, removed target names, and `extends` are covered in [Extending a theme](/en/themes/extending). The semantic icon table is still `icons`: a name points at an Iconify id.

```ts
const brand = defineTheme({
  name: 'brand',
  color: { accent: '#0064E0' },
  typography: { scale: { base: 14, ratio: 1.2 } },
  radius: { base: 4, multiplier: 1 },
  motion: { fast: 125, medium: 300, slow: 700, ratio: 0.75 },
})
```

For the tokens alone, call `expandColorScale`, `expandTypeScale`, `expandRadiusScale`, or `expandMotionScale`. They return the unprefixed names. Pass that object to `defineTheme()` as `tokens`.

## Best practices

| Guidance | Practices |
| --- | --- |
| Do | Pass token names with the `--` prefix, such as `--color-accent` or `--astryx-color-accent`. |
| Do | Use a `[light, dark]` tuple when the two modes differ. |
| Do | Nest `Theme` when one region needs a different theme. |
| Don't | Use `Theme` to flip a single image overlay. That is `MediaTheme`. |

## Examples

### Theme — Apply Theme

Wrap a subtree in Theme to apply a theme to every child component in that region.

```vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Card } from '@astryx-vue/core/Card'
import { Heading } from '@astryx-vue/core/Heading'
import { Section } from '@astryx-vue/core/Section'
import { Stack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
import { defineTheme, Theme } from '@astryx-vue/core/Theme'

const forestTheme = defineTheme({
  name: 'forest-docs',
  tokens: {
    '--color-accent': ['#15803D', '#86EFAC'],
    '--color-background-card': ['#FFFFFF', '#0F3D24'],
    '--color-text-primary': ['#052E16', '#DCFCE7'],
    '--color-text-secondary': ['#166534', '#BBF7D0'],
    '--color-border': ['#BBF7D0', '#15803D66'],
  },
})
</script>

<template>
  <Section variant="muted" :padding="4" :max-width="420">
    <Theme :theme="forestTheme">
      <Card :padding="4" width="100%">
        <Stack direction="vertical" :gap="3">
          <Heading :level="4">
            Forest workspace
          </Heading>
          <Text type="body" color="secondary">
            Wrap any subtree to apply a theme locally.
          </Text>
          <Button label="Create project" variant="primary" />
        </Stack>
      </Card>
    </Theme>
  </Section>
</template>
```

### Theme — Nested Theme

Nested Theme providers let a local region use a different theme without affecting the rest of the page.

```vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Card } from '@astryx-vue/core/Card'
import { Heading } from '@astryx-vue/core/Heading'
import { Section } from '@astryx-vue/core/Section'
import { Stack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
import { defineTheme, Theme } from '@astryx-vue/core/Theme'

const warmTheme = defineTheme({
  name: 'warm-docs',
  tokens: {
    '--color-accent': ['#D97706', '#FBBF24'],
    '--color-background-surface': ['#FFF7ED', '#1F1300'],
    '--color-background-card': ['#FFFBEB', '#2A1A05'],
    '--color-text-primary': ['#3B2F15', '#FEF3C7'],
    '--color-text-secondary': ['#92400E', '#FCD34D'],
    '--color-border': ['#FED7AA', '#92400E66'],
    '--radius-container': '20px',
  },
})

const forestTheme = defineTheme({
  name: 'forest-docs',
  tokens: {
    '--color-accent': ['#15803D', '#86EFAC'],
    '--color-background-surface': ['#F0FDF4', '#052E16'],
    '--color-background-card': ['#FFFFFF', '#0F3D24'],
    '--color-text-primary': ['#052E16', '#DCFCE7'],
    '--color-text-secondary': ['#166534', '#BBF7D0'],
    '--color-border': ['#BBF7D0', '#15803D66'],
    '--radius-container': '8px',
  },
})
</script>

<template>
  <Section variant="muted" :padding="4" :max-width="460">
    <Theme :theme="warmTheme">
      <Card :padding="4" width="100%">
        <Stack direction="vertical" :gap="4">
          <Stack direction="vertical" :gap="2">
            <Heading :level="4">
              Outer Warm theme
            </Heading>
            <Text type="body" color="secondary">
              The parent section uses Warm.
            </Text>
          </Stack>
          <Theme :theme="forestTheme">
            <Card :padding="3" width="100%">
              <Stack direction="vertical" :gap="2">
                <Text type="body" weight="bold">
                  Nested Forest section
                </Text>
                <Button label="Nested action" variant="primary" size="sm" />
              </Stack>
            </Card>
          </Theme>
        </Stack>
      </Card>
    </Theme>
  </Section>
</template>
```

### Theme — Switch Themes

Use state to switch the theme object passed to Theme and preview a different visual treatment.

```vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Card } from '@astryx-vue/core/Card'
import { Heading } from '@astryx-vue/core/Heading'
import { Section } from '@astryx-vue/core/Section'
import { Selector } from '@astryx-vue/core/Selector'
import { Stack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
import { defineTheme, Theme } from '@astryx-vue/core/Theme'
import { computed, ref } from 'vue'

const warmTheme = defineTheme({
  name: 'warm-docs',
  tokens: {
    '--color-accent': ['#D97706', '#FBBF24'],
    '--color-background-surface': ['#FFF7ED', '#1F1300'],
    '--color-background-card': ['#FFFBEB', '#2A1A05'],
    '--color-text-primary': ['#3B2F15', '#FEF3C7'],
    '--color-text-secondary': ['#92400E', '#FCD34D'],
    '--color-border': ['#FED7AA', '#92400E66'],
    '--radius-container': '20px',
  },
})

const forestTheme = defineTheme({
  name: 'forest-docs',
  tokens: {
    '--color-accent': ['#15803D', '#86EFAC'],
    '--color-background-surface': ['#F0FDF4', '#052E16'],
    '--color-background-card': ['#FFFFFF', '#0F3D24'],
    '--color-text-primary': ['#052E16', '#DCFCE7'],
    '--color-text-secondary': ['#166534', '#BBF7D0'],
    '--color-border': ['#BBF7D0', '#15803D66'],
    '--radius-container': '8px',
  },
})

const themes = {
  Warm: warmTheme,
  Forest: forestTheme,
}

const themeName = ref<'Warm' | 'Forest'>('Warm')
const theme = computed(() => themes[themeName.value])
const options = [
  { value: 'Warm', label: 'Warm' },
  { value: 'Forest', label: 'Forest' },
]
</script>

<template>
  <Section variant="muted" :padding="4" :max-width="420">
    <Stack direction="vertical" :gap="3">
      <Selector v-model="themeName" label="Theme" :options="options" />
      <Theme :theme="theme">
        <Card :padding="4" width="100%">
          <Stack direction="vertical" :gap="3">
            <Heading :level="4">
              {{ themeName }} preview
            </Heading>
            <Text type="body" color="secondary">
              Switching the theme object updates all tokens and component styles below the provider.
            </Text>
            <Button label="Save changes" variant="primary" />
          </Stack>
        </Card>
      </Theme>
    </Stack>
  </Section>
</template>
```

## Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `theme` | `ThemeDefinition` | — | Theme from `defineTheme()`. |
| `mode` | `'light' \| 'dark' \| 'system'` | `'system'` | Color mode. `system` follows the page and the OS. |

## Slots

| Name | Description |
| --- | --- |
| `default` | Content that inherits this theme. |

## defineTheme

| Field | Description |
| --- | --- |
| <code>name</code> | Theme name. Written to <code>data-astryx-theme</code>. |
| <code>tokens</code> | Semantic token overrides. A tuple is the light stop, then the dark stop. A listed name wins over a generated value. |
| <code>localTokens</code> | Values only this theme family uses, by full custom-property name. Names stay as written. A shared token name is refused. |
| <code>color</code> | Palette from an accent. Omit the accent to recolor only the neutrals. |
| <code>typography</code> | Fonts, and a type scale from base and ratio. |
| <code>radius</code> | Radius steps from base and multiplier. |
| <code>motion</code> | Durations from fast, medium, slow, and ratio. |
| <code>components</code> | Styles by theme target, <code>&lt;component-kebab&gt;-&lt;part&gt;</code>. The compiled sheet is <code>css</code> on the theme. |
| <code>adaptations</code> | Ordered rules that apply by width, pointer, contrast, or reduced motion. A later matching rule wins. |
| <code>extends</code> | Another theme from <code>defineTheme()</code>. Everything it resolved carries over. This theme's own values win. |
| <code>syntax</code> | Syntax colors for code in this subtree, unless a <code>SyntaxTheme</code> is closer. |
| <code>onDark</code> | Extra tokens and component styles for a dark <code>MediaTheme</code> surface. |
| <code>onLight</code> | Extra tokens and component styles for a light <code>MediaTheme</code> surface. |
| <code>icons</code> | Semantic icon name to Iconify id. Overrides by name and merges shallowly with the extended theme. |
| <code>indicators</code> | Indicator graphics, replaced by check, checkbox, or radio. One entry reaches every place that draws that name. |

## useTheme

`useTheme()` reads the nearest theme. Outside a `Theme`, the name is `neutral` and `token()` reads the document's custom properties. Values are the theme's root values; adaptation rules apply through CSS only. The returned object and `token` are created once, and `tokens` changes only when the theme or the mode does.

| Field | Description |
| --- | --- |
| <code>name</code> | Nearest theme name. |
| <code>mode</code> | Resolved <code>light</code> or <code>dark</code>. <code>system</code> is resolved. |
| <code>token(name)</code> | One token for the effective mode. A local token is read by its exact name. A chart color nobody set falls back to its default. |
| <code>tokens</code> | Tokens and local tokens declared by the nearest theme, resolved for the effective mode. |

## Template ref

The component exposes `element` with type `HTMLElement | null`.

## Native attributes

The root is a `div` with `display: contents`. `class` and `style` are applied to it. Other attributes are not forwarded.
