{
  "title": "Theming",
  "description": "Swap the brand color across every component by passing a NoriTheme to NoriProvider — choose a bundled preset or roll your own.",
  "url": "/docs/theming",
  "since": "0.4.0",
  "tags": [
    "theming",
    "customisation"
  ],
  "platform": "both",
  "source": "---\ntitle: Theming\ndescription: Swap the brand color across every component by passing a NoriTheme to NoriProvider — choose a bundled preset or roll your own.\nsince: 0.4.0\ntags: [theming, customisation]\nplatform: both\n---\n\nimport { Callout } from 'fumadocs-ui/components/callout';\nimport { Preview } from '@/components/preview';\n\n<Callout title=\"Try it now\">\n    See the floating swatch picker in the bottom-right corner? Click any\n    color and every component on the page re-renders against that preset.\n    Your choice persists across reloads (stored in `localStorage`).\n</Callout>\n\n## At a glance\n\n- A `NoriTheme` is a paired light/dark palette: `{ light: Theme, dark: Theme }`. The hook `useThemeColors()` resolves the right half based on the current color scheme.\n- Pass a theme to `<NoriProvider theme={...}>` (or `<ThemeProvider theme={...}>` directly) and every component below it picks it up — buttons, sliders, switches, toasts, the lot.\n- Six bundled presets — **teal** (default), **blue**, **rose**, **violet**, **orange**, **slate** — exported by name from `@nori-ui/core`.\n- Custom theme: spread `defaultTheme` and override the colors you care about. Most of the time you only need to touch the `primary` ramp.\n\n<Callout title=\"Limitation: className-only paths don't theme yet\">\n    The Tailwind preset compiles colors at build time. Components that\n    style themselves *only* via class names (e.g. `bg-semantic-interactive-primary`)\n    won't follow `<ThemeProvider>` overrides — they keep the build-time palette.\n\n    In practice, every interactive component in `@nori-ui/core` *also* sets\n    its key colors inline via `useThemeColors()`. Inline styles win on CSS\n    specificity, so the visible color matches the active theme. Wholesale\n    re-skin via CSS variables is on the roadmap.\n</Callout>\n\n## Themes change more than just color\n\nSpacing, border radius, font size, font weight, font family, line height\n— every dimensional and typographic token a component touches comes from\nthe active theme. Override the tokens in your custom theme and every\ncomponent reshapes accordingly:\n\n<Preview name=\"theming-tokens\" />\n\n## Use a preset\n\n```tsx\nimport { NoriProvider, blueTheme } from '@nori-ui/core/client';\n\nexport default function Layout({ children }) {\n    return <NoriProvider theme={blueTheme}>{children}</NoriProvider>;\n}\n```\n\nAvailable presets:\n\n```tsx\nimport {\n    tealTheme,\n    // default\n    blueTheme,\n    roseTheme,\n    violetTheme,\n    orangeTheme,\n    slateTheme,\n} from '@nori-ui/core/client';\n```\n\n`presetThemes` is also exported as an object keyed by name — handy for theme pickers:\n\n```tsx\nimport { presetThemes, type PresetThemeName } from '@nori-ui/core/client';\n\nconst [name, setName] = useState<PresetThemeName>('teal');\nreturn <NoriProvider theme={presetThemes[name]}>{children}</NoriProvider>;\n```\n\n## Build a custom theme\n\nThe presets only swap the `primary` ramp; the neutral / semantic / status\ncolors stay shared across all themes. Build your own by spreading the\ndefault and overriding what you need:\n\n```tsx\nimport { type NoriTheme, defaultTheme, NoriProvider } from '@nori-ui/core/client';\n\nconst myTheme: NoriTheme = {\n    light: {\n        ...defaultTheme.light,\n        color: {\n            ...defaultTheme.light.color,\n            primary: {\n                '50':  '#fff7ed',\n                '100': '#ffedd5',\n                '200': '#fed7aa',\n                '300': '#fdba74',\n                '400': '#fb923c',\n                '500': '#f97316',\n                '600': '#ea580c', // resting brand color\n                '700': '#c2410c', // hover\n                '800': '#9a3412', // pressed\n                '900': '#7c2d12',\n            },\n        },\n        semantic: {\n            ...defaultTheme.light.semantic,\n            interactive: {\n                ...defaultTheme.light.semantic.interactive,\n                primary: '#ea580c',\n                primaryHover: '#c2410c',\n                primaryPressed: '#9a3412',\n            },\n        },\n    },\n    dark: {\n        ...defaultTheme.dark,\n        // ... mirror with darker steps reading well against #18181b\n    },\n};\n\nexport default function App() {\n    return <NoriProvider theme={myTheme}>{/* ... */}</NoriProvider>;\n}\n```\n\n## Switching themes at runtime\n\nThemes live in React context, so swapping the prop is enough — every\ndescendant re-renders with the new palette. No CSS variable thrash, no\nflash on switch.\n\n```tsx\n'use client';\nimport { NoriProvider, presetThemes, type PresetThemeName } from '@nori-ui/core/client';\nimport { SegmentedControl } from '@nori-ui/core';\nimport { useState } from 'react';\n\nexport function ThemedApp({ children }) {\n    const [name, setName] = useState<PresetThemeName>('teal');\n    return (\n        <NoriProvider theme={presetThemes[name]}>\n            <SegmentedControl\n                value={name}\n                onChange={setName}\n                options={[\n                    { value: 'teal',   label: 'Teal' },\n                    { value: 'blue',   label: 'Blue' },\n                    { value: 'rose',   label: 'Rose' },\n                    { value: 'violet', label: 'Violet' },\n                ]}\n            />\n            {children}\n        </NoriProvider>\n    );\n}\n```\n\n## Theming + color scheme\n\nThemes are independent from the light/dark color scheme. The same\n`NoriTheme` covers both — `useColorScheme()` picks which half is active.\nToggle dark mode the way you already do (e.g. add `class=\"dark\"` to\n`<html>`); the active theme follows.\n\n## Reading theme colors in your own components\n\n```tsx\n'use client';\nimport { useThemeColors } from '@nori-ui/core/client';\n\nexport function MyChip({ children }) {\n    const colors = useThemeColors();\n    return (\n        <View style={{ backgroundColor: colors.semantic.interactive.primary }}>\n            <Text style={{ color: colors.semantic.text.inverted }}>{children}</Text>\n        </View>\n    );\n}\n```\n"
}
