npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dheme/react

v2.15.0

Published

React bindings for Dheme SDK with zero-FOUC theme application

Downloads

1,606

Readme

@dheme/react

React bindings for the Dheme Theme Generator API. Apply production-ready themes to your React app with a single provider — zero FOUC on any visit after the first.

Built for React SPAs (Vite, CRA, Remix SPA mode). For Next.js App Router, use @dheme/next instead.

Installation

npm install @dheme/react @dheme/sdk
yarn add @dheme/react @dheme/sdk
pnpm add @dheme/react @dheme/sdk

Requirements

| Dependency | Version | | ------------ | -------- | | react | >= 18 | | react-dom | >= 18 | | @dheme/sdk | >= 1.1.0 |

@dheme/sdk is included as a dependency and will be installed automatically. You only need to install it explicitly if you want to use the SDK client directly.

Quick Start

import { DhemeProvider, DhemeScript } from '@dheme/react';

function App() {
  return (
    <>
      <DhemeScript />
      <DhemeProvider apiKey="dheme_abc12345_..." theme="#3b82f6">
        <YourApp />
      </DhemeProvider>
    </>
  );
}

That's it. Your app now has 19 CSS variables applied to :root — fully compatible with shadcn/ui and Tailwind CSS.

How It Works

First visit (no cache)

  1. React mounts, DhemeProvider calls the Dheme API
  2. Theme is applied as CSS variables on :root
  3. Theme is cached in localStorage for next visit

Subsequent visits (cached — zero FOUC)

The provider applies the cached theme synchronously during React's first render — before the browser paints. No loading overlay is shown:

  1. React mounts, DhemeProvider reads localStorage and applies CSS variables immediately in its useState initializer
  2. isReady starts as true, children render with the correct theme from the first paint
  3. A background API request revalidates the cache without blocking anything

Adding <DhemeScript> for first-visit FOUC prevention

For apps that want zero FOUC even on the very first visit (before the cache is populated), add <DhemeScript> to your document <head>. It injects a tiny ~800-byte blocking script that runs before React loads:

// index.html / _document.tsx
<head>
  <DhemeScript defaultMode="dark" />
</head>

Without it, the first visit shows a loading overlay while the API call completes. With it, no overlay is shown even on the first visit (if there is a cache from a previous visit in the same browser).

Components

<DhemeProvider>

The main provider. Manages theme state, API calls, caching, and CSS variable application.

<DhemeProvider
  apiKey="dheme_abc12345_..." // Required — your Dheme API key
  theme="#3b82f6" // Primary color (auto-generates on mount)
  themeParams={{
    // Optional generation params
    radius: 0.75,
    saturationAdjust: 10,
    secondaryColor: '#10b981',
    borderIsColored: false,
    tailwindVersion: 'v4', // 'v3' | 'v4' (default: 'v4')
  }}
  defaultMode="light" // 'light' | 'dark' (default: 'light')
  persist={true} // Cache in localStorage (default: true)
  autoApply={true} // Apply CSS vars automatically (default: true)
  onThemeChange={(theme) => {}} // Callback when theme changes
  onModeChange={(mode) => {}} // Callback when mode changes
  onError={(error) => {}} // Callback on error
>
  <App />
</DhemeProvider>

| Prop | Type | Default | Description | | --------------- | ---------------------------------------- | --------- | ---------------------------------------------------- | | apiKey | string | - | Required. Your Dheme API key. | | theme | string | - | Primary HEX color. Auto-generates on mount. | | themeParams | Omit<GenerateThemeRequest, 'theme'> | - | Additional generation parameters. | | defaultMode | 'light' \| 'dark' | 'light' | Initial color mode. | | baseUrl | string | - | Override API base URL. | | persist | boolean | true | Cache theme in localStorage. | | autoApply | boolean | true | Apply CSS variables to :root. | | onThemeChange | (theme: GenerateThemeResponse) => void | - | Called when theme data changes. | | onModeChange | (mode: ThemeMode) => void | - | Called when mode changes. | | onError | (error: Error) => void | - | Called on API errors. | | loadingContent | React.ReactNode | - | Content rendered inside the loading wrapper on first API call. |

themeParams.tailwindVersion controls the CSS variable format applied to :root. Use 'v3' for projects that wrap variables with hsl(var(--token)) (Tailwind v3 / shadcn/ui default), or 'v4' (default) for projects that use var(--token) directly (Tailwind v4 / @theme inline).

<DhemeScript>

Optional blocking script for zero FOUC on the very first visit. On cached visits, the provider already handles FOUC prevention internally — DhemeScript adds protection for first-visit scenarios.

<DhemeScript
  defaultMode="light" // Must match the defaultMode in DhemeProvider
  nonce="abc123" // CSP nonce (optional)
/>

Place it in your HTML <head> before React loads. The defaultMode must match the value used in <DhemeProvider>.

| Prop | Type | Default | Description | | ------------- | ------------------- | --------- | ---------------------------------------------------------------- | | defaultMode | 'light' \| 'dark' | 'light' | Fallback mode when no preference is stored. Match DhemeProvider. | | nonce | string | - | CSP nonce for the script. |

Hooks

useTheme()

Read theme data. Only re-renders when theme data or mode changes — not when loading state changes.

import { useTheme } from '@dheme/react';

function MyComponent() {
  const { theme, mode, isReady } = useTheme();

  if (!isReady) return <Skeleton />;

  return <p>Primary: {theme.colors[mode].primary.h}°</p>;
}

| Return | Type | Description | | --------- | ------------------------------- | ---------------------------- | | theme | GenerateThemeResponse \| null | The full theme data. | | mode | 'light' \| 'dark' | Current color mode. | | isReady | boolean | true once theme is loaded. |

useThemeActions()

Access actions and loading state. Components using this hook re-render on action state changes — components using only useTheme() do not.

import { useThemeActions } from '@dheme/react';

function ThemeToggle() {
  const { setMode, isLoading } = useThemeActions();

  return (
    <button disabled={isLoading} onClick={() => setMode(mode === 'light' ? 'dark' : 'light')}>
      Toggle
    </button>
  );
}

| Return | Type | Description | | --------------- | ------------------------------------------------- | ------------------------ | | generateTheme | (params: GenerateThemeRequest) => Promise<void> | Generate a new theme. | | setMode | (mode: ThemeMode) => void | Switch light/dark mode. | | clearTheme | () => void | Clear theme and cache. | | isLoading | boolean | true during API call. | | error | Error \| null | Last error, if any. | | client | DhemeClient | Raw SDK client instance. |

useGenerateTheme()

Convenience hook with local loading state — useful when multiple components trigger generation independently.

import { useGenerateTheme } from '@dheme/react';

function ColorPicker() {
  const { generateTheme, isGenerating, error } = useGenerateTheme();

  return (
    <button disabled={isGenerating} onClick={() => generateTheme({ theme: '#ef4444' })}>
      {isGenerating ? 'Generating...' : 'Apply Red'}
    </button>
  );
}

useDhemeClient()

Direct access to the DhemeClient instance for advanced operations (e.g., getUsage()).

import { useDhemeClient } from '@dheme/react';

function UsageInfo() {
  const client = useDhemeClient();
  const [usage, setUsage] = useState(null);

  useEffect(() => {
    client.getUsage().then(({ data }) => setUsage(data));
  }, [client]);

  return usage ? <p>{usage.remaining} requests left</p> : null;
}

Context Splitting (Performance)

The provider uses two separate React contexts to minimize re-renders:

| Context | Contains | Changes when | | --------------------- | ------------------------------------------------ | -------------------------- | | ThemeDataContext | theme, mode, isReady | Theme data or mode changes | | ThemeActionsContext | generateTheme, setMode, isLoading, error | Actions are triggered |

Components using useTheme() (data) do not re-render when isLoading changes. Components using useThemeActions() (actions) do not re-render when theme data changes.

This prevents cascading re-renders in large component trees.

Caching

Themes are cached in localStorage with deterministic keys based on input parameters:

same input params → same cache key → same theme

The cache key is derived from: theme, secondaryColor, radius, saturationAdjust, lightnessAdjust, contrastAdjust, cardIsColored, backgroundIsColored, borderIsColored.

Stale-while-revalidate

On cached visits, the provider:

  1. Serves the cached theme immediately (zero latency)
  2. Fires a background API request to check for updates
  3. Only updates the UI if the response differs from the cache

This ensures instant page loads while keeping themes fresh.

Mode Switching

Switching between light and dark mode does not make an API call. Both colors.light and colors.dark are included in a single API response, so mode switching is instant.

const { setMode } = useThemeActions();

// Instant — no network request
setMode('dark');

The provider also syncs the dark class on <html> automatically.

CSS Variables

The provider sets 19 CSS variables + --radius on :root. The value format depends on themeParams.tailwindVersion:

Tailwind v4 (default) — wrapped hsl(), use with var(--token):

:root {
  --background: hsl(0 0% 100%);
  --foreground: hsl(222.2 84% 4.9%);
  --primary: hsl(221.2 83.2% 53.3%);
  --primary-foreground: hsl(210 40% 98%);
  /* ... 15 more tokens */
  --radius: 0.5rem;
}

Tailwind v3 (tailwindVersion: 'v3') — bare channels, use with hsl(var(--token)):

:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 221.2 83.2% 53.3%;
  --primary-foreground: 210 40% 98%;
  /* ... 15 more tokens */
  --radius: 0.5rem;
}

ThemeGenerator

A floating FAB (Floating Action Button) that lets users generate and preview themes in real time — directly inside your app. No external dependencies beyond React itself.

import { DhemeProvider, ThemeGenerator } from '@dheme/react';

function App() {
  return (
    <DhemeProvider apiKey="..." theme="#3b82f6">
      <MyApp />
      <ThemeGenerator />
    </DhemeProvider>
  );
}

The component renders as a pill in the corner of the screen. Clicking it expands a panel with color pickers, sliders, and toggles that call generateTheme() in real time with per-parameter debounce.

Props

<ThemeGenerator
  defaultTheme="#4332f6" // Initial primary color
  defaultSecondaryColor="#ab67f1" // Initial secondary color
  defaultSecondaryEnabled={false} // Whether secondary starts enabled
  defaultSaturation={10} // Initial saturation adjust (-100 to 100)
  defaultLightness={2} // Initial lightness adjust (-100 to 100)
  defaultRadius={0} // Initial border radius (0 to 2 rem)
  defaultBackgroundIsColored={false} // Initial colorful background toggle
  position="bottom-right" // 'bottom-right' | 'bottom-left'
  open={isOpen} // Controlled open state (optional)
  onOpenChange={setIsOpen} // Controlled open callback (optional)
  labels={{
    // i18n overrides (all optional)
    title: 'Theme Generator',
    primary: 'Primary Color',
    secondary: 'Secondary Color',
    saturation: 'Vibrancy',
    lightness: 'Brightness',
    reset: 'Restore defaults',
  }}
  className="my-fab" // Extra class on the container (optional)
/>

| Prop | Type | Default | Description | | ---------------------------- | --------------------------------- | ---------------- | ------------------------------------------------ | | defaultTheme | string | '#4332f6' | Initial primary HEX color. | | defaultSecondaryColor | string | '#ab67f1' | Initial secondary HEX color. | | defaultSecondaryEnabled | boolean | false | Whether secondary color starts enabled. | | defaultSaturation | number | 10 | Initial saturation adjust (-100–100). | | defaultLightness | number | 2 | Initial lightness adjust (-100–100). | | defaultRadius | number | 0 | Initial border radius (0–2 rem). | | defaultBackgroundIsColored | boolean | false | Initial state of the colorful background toggle. | | position | 'bottom-right' \| 'bottom-left' | 'bottom-right' | Corner to anchor the FAB. | | open | boolean | — | Controlled open state. Omit for uncontrolled. | | onOpenChange | (open: boolean) => void | — | Called when open state changes. | | labels | ThemeGeneratorLabels | See below | Override any UI text for i18n. | | className | string | — | Extra CSS class on the fixed container. |

Default labels

| Key | Default | | -------------------- | ----------------------------------------------------------------------------- | | title | 'Theme Generator' | | description | 'Generate complete themes from a single color. Changes apply in real time.' | | baseColors | 'Base Colors' | | primary | 'Primary' | | secondary | 'Secondary' | | optional | 'Optional' | | fineTuning | 'Fine Tuning' | | saturation | 'Saturation' | | lightness | 'Lightness' | | borderRadius | 'Border Radius' | | advancedOptions | 'Advanced Options' | | colorfulCard | 'Colorful Card' | | colorfulBackground | 'Colorful Background' | | colorfulBorder | 'Colorful Border' | | reset | 'Reset' | | fabPrimaryLabel | 'Primary' |

How it works

Real-time generation with debounce

Each parameter has its own debounce timer. Dragging the saturation slider fires an API call 200ms after the user stops — not on every frame. Color pickers debounce at 150ms.

| Control | Debounce | | ------------------------------------------ | ------------------------ | | Color pickers | 150ms | | Sliders (saturation, lightness, radius) | 200ms | | Boolean toggles (card, background, border) | None — fires immediately | | Secondary color enable/disable | None — fires immediately |

No re-render loops

State local to the component (localPrimary, localSaturation, …) is the source of truth for the controls. The component does not read back from useTheme(). This prevents a loop where generateTheme() → theme state update → re-render → re-initialize state → generateTheme() again.

Zero external dependencies

  • Icons: inline SVG (no lucide-react)
  • Color picker: built-in gradient + hue slider using pointer events (no react-colorful)
  • Styling: inline styles using hsl(var(--...)) CSS variables — automatically matches the active theme

Controlled vs uncontrolled

Omit open / onOpenChange for fully uncontrolled behavior. Provide both for external control:

const [open, setOpen] = useState(false);

<ThemeGenerator open={open} onOpenChange={setOpen} />
<button onClick={() => setOpen(true)}>Open Theme Editor</button>

Placement

ThemeGenerator must be a descendant of <DhemeProvider> — it consumes useThemeActions() internally.

// ✅ Correct
<DhemeProvider ...>
  <App />
  <ThemeGenerator />
</DhemeProvider>

// ❌ Wrong — outside the provider
<>
  <ThemeGenerator />
  <DhemeProvider ...>
    <App />
  </DhemeProvider>
</>

Utilities

themeToCSS(theme, mode, tailwindVersion?)

Convert a GenerateThemeResponse to a CSS variable assignment string.

import { themeToCSS } from '@dheme/react';

// Tailwind v4 (default) — hsl() wrapped
const css = themeToCSS(theme, 'light');
// "--background:hsl(0 0% 100%);--foreground:hsl(222.2 84% 4.9%);..."

// Tailwind v3 — bare channels
const css = themeToCSS(theme, 'light', 'v3');
// "--background:0 0% 100%;--foreground:222.2 84% 4.9%;..."

applyThemeCSSVariables(theme, mode, tailwindVersion?)

Manually apply CSS variables to :root.

import { applyThemeCSSVariables } from '@dheme/react';

applyThemeCSSVariables(theme, 'dark'); // Tailwind v4 (default)
applyThemeCSSVariables(theme, 'dark', 'v3'); // Tailwind v3

removeThemeCSSVariables()

Remove all Dheme CSS variables from :root.

buildCacheKey(params)

Generate the deterministic cache key for a set of params.

import { buildCacheKey } from '@dheme/react';

const key = buildCacheKey({ theme: '#3b82f6', radius: 0.75 });

Full Example (Vite)

// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { DhemeProvider, DhemeScript } from '@dheme/react';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <DhemeScript />
    <DhemeProvider
      apiKey={import.meta.env.VITE_DHEME_API_KEY}
      theme="#3b82f6"
      themeParams={{ radius: 0.5 }}
    >
      <App />
    </DhemeProvider>
  </React.StrictMode>
);
// App.tsx
import { useTheme, useThemeActions } from '@dheme/react';

export default function App() {
  const { theme, mode, isReady } = useTheme();
  const { setMode } = useThemeActions();

  if (!isReady) return <div>Loading theme...</div>;

  return (
    <div>
      <h1
        style={{
          color: `hsl(${theme.colors[mode].primary.h} ${theme.colors[mode].primary.s}% ${theme.colors[mode].primary.l}%)`,
        }}
      >
        Dheme Theme
      </h1>
      <button onClick={() => setMode(mode === 'light' ? 'dark' : 'light')}>
        {mode === 'light' ? 'Dark' : 'Light'} Mode
      </button>
    </div>
  );
}

TypeScript

All types are exported:

import type {
  ThemeMode,
  ThemeDataState,
  ThemeActionsState,
  DhemeProviderProps,
  DhemeScriptProps,
  GenerateThemeRequest,
  GenerateThemeResponse,
  ColorTokens,
  HSLColor,
} from '@dheme/react';

Related Packages

| Package | Description | When to use | | -------------- | ----------------------------- | -------------------------- | | @dheme/sdk | Core TypeScript SDK | Direct API access, Node.js | | @dheme/react | React bindings (this package) | Vite, CRA, React SPAs | | @dheme/next | Next.js App Router bindings | Next.js 14+ with SSR |

License

MIT