structyl
@structyl/themes

Color Presets

10 built-in accent presets that override the primary color tokens at runtime — no theme rebuild needed. Extend with your own brand colors.

bash
pnpm add @structyl/themes

Built-in presets

COLOR_PRESETS is a typed as const array of all 10 built-in accent colors. Import it for rendering your own picker UI.

tsx
import { COLOR_PRESETS } from '@structyl/themes';

// [
//   { id: 'structyl',   name: 'Structyl',   hex: '#5754a3' },
//   { id: 'indigo', name: 'Indigo', hex: '#6366f1' },
//   { id: 'ocean',  name: 'Ocean',  hex: '#0284c7' },
//   { id: 'rose',   name: 'Rose',   hex: '#e11d48' },
//   { id: 'forest', name: 'Forest', hex: '#16a34a' },
//   { id: 'sunset', name: 'Sunset', hex: '#f97316' },
//   { id: 'violet', name: 'Violet', hex: '#7c3aed' },
//   { id: 'slate',  name: 'Slate',  hex: '#475569' },
//   { id: 'ember',  name: 'Ember',  hex: '#be123c' },
//   { id: 'zinc',   name: 'Zinc',   hex: '#71717a' },
// ]

useColorPreset

The primary way to work with presets in React. Must be used inside a ThemeProvider. Persists the selection to localStorage and automatically re-applies the preset after every theme or color-mode change.

Click a preset — every primary-color token on this page updates live.

Primary button
Outline button
Muted button
tsx
'use client';
import { useColorPreset, COLOR_PRESETS } from '@structyl/themes';

function AccentPicker() {
  const { activeId, setPreset, clearPreset } = useColorPreset();

  return (
    <div className="flex gap-2">
      {COLOR_PRESETS.map(({ id, name, hex }) => (
        <button
          key={id}
          onClick={() => setPreset(id, hex)}
          title={name}
          style={{ background: hex }}
          className="h-7 w-7 rounded-full"
          aria-pressed={activeId === id}
        />
      ))}
      {activeId && (
        <button onClick={clearPreset}>Reset</button>
      )}
    </div>
  );
}

Options

OptionTypeDefaultDescription
extraPresetsColorPreset[][]Additional presets to add on top of the 10 built-ins. Built-ins always come first.
storageKeystring'structyl-color-preset'localStorage key used to persist the active preset. Override when you need multiple independent pickers on the same origin.

Return value

PropertyTypeDescription
presetsColorPreset[]All available presets (built-ins + extraPresets).
activeIdstring | nullID of the active preset, or null when using the default theme.
activePresetColorPreset | nullFull preset object for the active ID.
setPreset(id, hex) => voidApply a preset — updates CSS vars and persists to localStorage.
clearPreset() => voidRemove the override and restore the base theme's primary colors.

createColorPreset

Factory for creating typed preset objects. Pass the result to extraPresets in useColorPreset.

A custom #ff5500 preset created with createColorPreset.

tsx
import { createColorPreset, useColorPreset } from '@structyl/themes';

const brandPreset = createColorPreset('brand', 'Brand Blue', '#1a6cf0');
const seasonalPreset = createColorPreset('holiday', 'Holiday Red', '#c0392b');

function MyPicker() {
  const { presets, activeId, setPreset, clearPreset } = useColorPreset({
    extraPresets: [brandPreset, seasonalPreset],
  });
  // presets = [...COLOR_PRESETS, brandPreset, seasonalPreset]
}

Imperative API

Use these utilities outside of React (e.g. in event handlers, server actions, or non-React contexts). Both are no-ops in SSR environments.

tsx
import { applyColorPreset, clearColorPreset } from '@structyl/themes';

// Apply any hex color as the primary accent
applyColorPreset('#6366f1');
// Sets --color-primary, --color-ring, --color-primary-hover,
//       --color-primary-active, --color-primary-fg on <html>

// Remove the override, restoring the active theme's base colors
clearColorPreset();

Types

ts
import type { ColorPreset, ColorPresetId } from '@structyl/themes';

// Interface for any preset object (built-in or custom)
interface ColorPreset {
  id: string;
  name: string;
  hex: string;
}

// Union of all built-in preset IDs
type ColorPresetId =
  | 'structyl' | 'indigo' | 'ocean' | 'rose' | 'forest'
  | 'sunset' | 'violet' | 'slate' | 'ember' | 'zinc';

Next.js App Router

Client Component required

useColorPreset uses React state and effects, so any component that calls it must be marked 'use client'. The imperative applyColorPreset / clearColorPreset functions are safe to import from any module — they guard against SSR automatically.

tsx
'use client'; // required

import { useColorPreset } from '@structyl/themes';

export function AccentPicker() {
  const { activeId, setPreset } = useColorPreset();
  // ...
}

How it works

Presets work by overriding five CSS custom properties on document.documentElement after the ThemeProvider applies its base tokens:

css
/* Applied by applyColorPreset('#6366f1') */
:root {
  --color-primary:        239 84% 67%;
  --color-ring:           239 84% 67%;
  --color-primary-hover:  239 84% 73%;
  --color-primary-active: 239 84% 59%;
  --color-primary-fg:     0 0% 100%;   /* white or dark, auto-calculated via WCAG */
}

Because useColorPreset watches theme and resolvedMode from useTheme, it automatically re-applies the override whenever the user switches themes or color modes — the base theme resets first, then the preset overwrites the five primary vars.