@docyrus/theme-provider
v0.1.0
Published
React theme provider with system mode support and embedded Docyrus theme presets
Downloads
416
Keywords
Readme
@docyrus/theme-provider
React theme provider for Docyrus applications. It combines light / dark / system mode handling with a second theme layer for branded preset themes, so applications can switch both color mode and design preset without reimplementing the runtime.
The package ships with five built-in presets taken from examples-web-app-react-spa:
docyrus-defaultshadcn-defaultastrovistadarkforgeattornaid
Applications can register additional themes or override built-in themes by passing their own theme definitions to the provider.
Features
light,dark, andsystemmode support- Separate preset-theme selection via
data-theme - Inline boot script to prevent theme flicker on initial load
- Built-in preset CSS injection at runtime
- App-level custom themes with typed theme definitions
- Persistent storage for both mode and preset theme
- React hook for reading and updating theme state
- Publishable client wrapper for Next.js / RSC-friendly consumption
Installation
npm install @docyrus/theme-provider reactpnpm add @docyrus/theme-provider reactPeer Dependencies
react>= 18.0.0
Quick Start
import { ThemeProvider } from '@docyrus/theme-provider';
export function AppProviders({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider
defaultTheme="system"
defaultColorTheme="docyrus-default"
disableTransitionOnChange
>
{children}
</ThemeProvider>
);
}With the default configuration, the provider:
- stores the mode in
localStorageundertheme - stores the preset theme in
localStorageunderdocyrus-color-theme - toggles
class="light|dark"ondocument.documentElement - toggles
data-theme="<preset-id>"ondocument.documentElement
Using the Hook
import { useTheme } from '@docyrus/theme-provider';
export function ThemeSwitcher() {
const {
theme,
setTheme,
resolvedTheme,
colorTheme,
setColorTheme,
availableThemes
} = useTheme();
return (
<div>
<p>Resolved mode: {resolvedTheme}</p>
<button onClick={() => setTheme('light')}>Light</button>
<button onClick={() => setTheme('dark')}>Dark</button>
<button onClick={() => setTheme('system')}>System</button>
<select
value={colorTheme}
onChange={event => setColorTheme(event.target.value)}
>
{availableThemes.map(themeDefinition => (
<option key={themeDefinition.id} value={themeDefinition.id}>
{themeDefinition.name}
</option>
))}
</select>
</div>
);
}Adding Custom Themes
Applications can extend the built-in presets by passing additional theme definitions.
import {
ThemeProvider,
defaultThemeDefinitions,
type ThemeDefinition
} from '@docyrus/theme-provider';
const baseTheme = defaultThemeDefinitions.find(theme => theme.id === 'docyrus-default');
if (!baseTheme || !baseTheme.dark) {
throw new Error('docyrus-default theme is required');
}
const signalGridTheme: ThemeDefinition = {
id: 'signal-grid',
name: 'Signal Grid',
light: {
...baseTheme.light,
'--background': 'oklch(0.984 0.008 215)',
'--primary': 'oklch(0.59 0.19 252)',
'--font-sans': "'Space Grotesk', Inter, sans-serif"
},
dark: {
...baseTheme.dark,
'--background': 'oklch(0.16 0.03 250)',
'--primary': 'oklch(0.73 0.14 245)',
'--font-sans': "'Space Grotesk', Inter, sans-serif"
}
};
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider themes={[signalGridTheme]}>
{children}
</ThemeProvider>
);
}Override Behavior
Theme merging is keyed by theme.id.
- new
idvalues append themes - existing
idvalues replace the built-in definition
That means a project can keep the shipped theme catalog and selectively override only one preset.
Styling Model
The provider separates two concerns:
1. Theme mode
Mode is applied through the attribute prop, which defaults to class.
By default:
lightadds.lightdarkadds.darksystemresolves to either.lightor.dark
2. Preset theme
Preset themes are applied through the themeAttribute prop, which defaults to data-theme.
By default:
docyrus-defaultbecomesdata-theme="docyrus-default"astrovistabecomesdata-theme="astrovista"
This allows CSS rules like:
[data-theme='astrovista'] {
--background: oklch(0.9383 0.0042 236.4993);
}
[data-theme='astrovista'].dark {
--background: oklch(0.2178 0 0);
}Provider Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| themes | ThemeDefinition[] | [] | Additional or overriding preset themes |
| includeDefaultThemes | boolean | true | Include the five built-in presets |
| defaultTheme | 'light' \| 'dark' \| 'system' | 'system' | Initial mode |
| defaultColorTheme | string | 'docyrus-default' | Initial preset theme |
| forcedTheme | 'light' \| 'dark' \| 'system' | undefined | Force the active mode |
| forcedColorTheme | string | undefined | Force the active preset theme |
| attribute | Attribute \| Attribute[] | 'class' | HTML attribute(s) used for mode |
| themeAttribute | Attribute | 'data-theme' | HTML attribute used for preset themes |
| modeStorageKey | string | 'theme' | Local storage key for mode |
| colorThemeStorageKey | string | 'docyrus-color-theme' | Local storage key for preset theme |
| enableSystem | boolean | true | Resolve system from prefers-color-scheme |
| enableColorScheme | boolean | true | Set document.documentElement.style.colorScheme |
| disableTransitionOnChange | boolean | false | Temporarily disable transitions during switches |
| modeValue | ModeValueObject | undefined | Custom DOM values for light / dark |
| themeValue | ThemeValueObject | undefined | Custom DOM values for preset themes |
| nonce | string | undefined | CSP nonce for injected script and style |
| scriptProps | ScriptProps | undefined | Extra props for the boot script |
Exported Types and Helpers
import {
ThemeProvider,
useTheme,
DEFAULT_COLOR_THEME,
defaultThemeDefinitions,
mergeThemeDefinitions,
serializeThemeDefinitions,
type ThemeDefinition,
type ThemeMode,
type ThemeProviderProps,
type ThemeVariables
} from '@docyrus/theme-provider';defaultThemeDefinitions
The built-in preset catalog as typed ThemeDefinition[].
mergeThemeDefinitions(baseThemes, additionalThemes)
Returns a merged theme array keyed by theme.id.
serializeThemeDefinitions(themes, attribute, valueMap?)
Serializes theme definitions into CSS text. Useful if an application wants to inspect or reuse the generated CSS.
Recommended CSS Token Setup
The provider only manages DOM state and preset CSS variables. Applications should still map those variables into their own design system tokens. With Tailwind v4, a common pattern is:
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-border: var(--border);
--font-sans: var(--font-sans);
}Notes
- The package is intentionally React-only. It does not ship a framework-agnostic core.
- The provider injects the preset theme CSS so consuming projects do not need to copy the built-in definitions into app stylesheets.
- If a project wants only custom themes, set
includeDefaultThemes={false}.
Development
pnpm -C packages/theme-provider dev
pnpm -C packages/theme-provider build
pnpm -C packages/theme-provider lint
pnpm -C packages/theme-provider typecheck