← Components/primitives

Theme

ThemeScope + IdPrefix providers — primitives.

primitivesstablea11y: untested3/3 coverageEdit on GitHub ↗
primitives-theme--defaultOpen all variants →

When to use

  • A card / dialog / preview that should render in a non-default theme regardless of the global setting (Mission Control's brand-preview tile rendering every brand × tone in one screen).
  • The Storybook themes addon's per-story preview frame.
  • A dark-on-light callout block inside a g100 page (or vice versa).

### When not to use

  • Application-level theme control (system color-scheme detection, localStorage persistence, tone toggle) → use the ThemeProvider from @avala/design's hooks barrel instead.
  • A single component that wants different styles per state — that's a CVA variant axis, not a theme override.

### API

  • brandavala | investor | learning | pay | tickets | chat | operations | neutral. Default avala.
  • toneg10 | g100. Default g100.
  • asdiv (default, wraps children with data-theme and data-brand attributes) or none (provides context only, no DOM wrapper).
  • All native <div> props pass through.

### Examples

`tsx import { ThemeScope, useScopedTheme } from "@avala/design";

// Render a brand-preview tile in g10 inside an otherwise-g100 page. <ThemeScope brand="avala" tone="g10" className="rounded-sm border p-4"> <Card> <h3>Light tone preview</h3> </Card> </ThemeScope>

// Read the current scope inside a component. function BrandLogo() { const { brand } = useScopedTheme(); return <img src={/logos/${brand}.svg} alt="" />; } `

### Important: tokens come from imported CSS

ThemeScope *announces* the active theme to descendants but does NOT swap the underlying token values. Today the brand-tone CSS files emit declarations against :root. To switch tokens at runtime, consumers must either:

1. Import all candidate brand-tone CSS files, then apply a [data-theme=…] / [data-brand=…] selector ramp on top (planned Phase 4 scoped-CSS emit). 2. Dynamically swap the <link rel="stylesheet"> for the target tone — useScopedTheme() returns the active identifiers so consumer code can drive the swap.

When not to use

  • Application-level theme control (system color-scheme detection, localStorage persistence, tone toggle) → use the ThemeProvider from @avala/design's hooks barrel instead.
  • A single component that wants different styles per state — that's a CVA variant axis, not a theme override.

### API

  • brandavala | investor | learning | pay | tickets | chat | operations | neutral. Default avala.
  • toneg10 | g100. Default g100.
  • asdiv (default, wraps children with data-theme and data-brand attributes) or none (provides context only, no DOM wrapper).
  • All native <div> props pass through.

### Examples

`tsx import { ThemeScope, useScopedTheme } from "@avala/design";

// Render a brand-preview tile in g10 inside an otherwise-g100 page. <ThemeScope brand="avala" tone="g10" className="rounded-sm border p-4"> <Card> <h3>Light tone preview</h3> </Card> </ThemeScope>

// Read the current scope inside a component. function BrandLogo() { const { brand } = useScopedTheme(); return <img src={/logos/${brand}.svg} alt="" />; } `

### Important: tokens come from imported CSS

ThemeScope *announces* the active theme to descendants but does NOT swap the underlying token values. Today the brand-tone CSS files emit declarations against :root. To switch tokens at runtime, consumers must either:

1. Import all candidate brand-tone CSS files, then apply a [data-theme=…] / [data-brand=…] selector ramp on top (planned Phase 4 scoped-CSS emit). 2. Dynamically swap the <link rel="stylesheet"> for the target tone — useScopedTheme() returns the active identifiers so consumer code can drive the swap.