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

@12nil/theme-registry-package

v0.1.5

Published

Theme registry and React provider utilities

Readme

Runtime Theme Registry

Runtime Theme Registry is a TypeScript-first theming runtime for React applications.

It provides:

  • Runtime theme registration and switching
  • React integration with ThemeProvider and useTheme
  • Legacy and semantic token schemas
  • Plugin contributions (icons, fonts, spacing, radii, typography)
  • Brand governance with strict registration, runtime guardrails, and CI audits
  • Composition layers, async loading, cache helpers, and hydration helpers

Table of Contents

  • Installation
  • Quick Start
  • Theme Models
  • React APIs
  • Theme Switcher Components
  • Registry APIs
  • Brand Governance Toolkit
  • CLI
  • Tailwind v4 Integration
  • Advanced Workflows
  • API Overview
  • Development
  • Related Docs

Installation

npm install @12nil/theme-registry-package

Peer dependency:

  • react >= 18

Quick Start

Create and register at least one theme before rendering ThemeProvider.

import { themeRegistry } from '@12nil/theme-registry-package'

themeRegistry.register({
  name: 'default',
  modes: {
    light: {
      primary: '#111827',
      secondary: '#374151',
      background: '#ffffff',
      text: '#111827',
      accent: '#2563eb',
      muted: '#6b7280',
      error: '#dc2626',
      warning: '#f59e0b',
      success: '#16a34a',
      info: '#0ea5e9',
    },
    dark: {
      primary: '#e5e7eb',
      secondary: '#9ca3af',
      background: '#030712',
      text: '#e5e7eb',
      accent: '#60a5fa',
      muted: '#6b7280',
      error: '#f87171',
      warning: '#fbbf24',
      success: '#4ade80',
      info: '#38bdf8',
    },
  },
})

themeRegistry.setFallback({
  themeName: 'default',
  modeName: 'light',
})
import React from 'react'
import { ThemeProvider, ThemeSwitcherStyled } from '@12nil/theme-registry-package'

export default function App() {
  return (
    <ThemeProvider>
      <div style={{ padding: 24 }}>
        <ThemeSwitcherStyled title="Theme Controls" subtitle="Choose theme and mode" />
        <h1 style={{ color: 'var(--color-theme-text)' }}>Hello Theme</h1>
      </div>
    </ThemeProvider>
  )
}

Theme Models

Theme tokens support two schemas.

  1. Legacy palette

Required keys:

  • primary
  • secondary
  • background
  • text
  • accent
  • muted
  • error
  • warning
  • success
  • info

Optional key:

  • tertiary
  1. Semantic tokens (v2)

Required categories:

  • colors
  • surface
  • text
  • border

Each category has required keys validated by the registry.

React APIs

ThemeProvider responsibilities:

  • Loads registered themes from themeRegistry
  • Restores persisted selection from localStorage
  • Resolves invalid selections via fallback rules
  • Applies resolved CSS variables to document root

Persistence keys:

  • runtime-theme-registry-theme
  • runtime-theme-registry-mode

ThemeProvider props:

  • injectUtilityClasses?: boolean (default true)

useTheme returns:

  • currentTheme
  • currentMode
  • themes
  • availableModes
  • setTheme(themeName, modeName)
  • setMode(modeName)
  • getThemeColors()

Theme Switcher Components

Exports:

  • ThemeSwitcher
  • ThemeSwitcherStyled

Common switcher props:

  • themeLabel
  • modeLabel
  • showThemeSelector (default true)
  • showModeSelector (default true)
  • modeSelectorVariant: select | buttons
  • modeIcons?: Partial<Record<string, React.ReactNode>>
  • modeIconOnly?: boolean
  • onThemeChanged
  • onModeChanged

Mode-only icon example:

import { ThemeProvider, ThemeSwitcherStyled } from '@12nil/theme-registry-package'
import { Moon, Sun } from 'lucide-react'

export default function App() {
  return (
    <ThemeProvider>
      <ThemeSwitcherStyled
        showThemeSelector={false}
        showModeSelector
        modeSelectorVariant="buttons"
        modeIconOnly
        modeIcons={{
          light: <Sun size={16} />,
          dark: <Moon size={16} />,
        }}
      />
    </ThemeProvider>
  )
}

Registry APIs

Core registration and lookup:

  • register, replace, merge, unregister
  • get, getAll, has
  • getTokens, getModes, getVariants
  • setCurrent, getCurrent
  • setFallback, resolveSelection

Dynamic theme extension:

  • createTheme
  • registerMode
  • registerModes

Plugins:

  • registerPlugin, unregisterPlugin
  • getPlugin, getPlugins
  • getPluginContributions, getMergedPluginContributions

Composition:

  • registerCompositionLayer
  • getCompositionLayer, getCompositionLayers
  • compose

Loading and cache:

  • load(source, options)
  • clearLoadCache(cacheKey?)

Hydration helpers:

  • getInitialThemeAttributes
  • hydrateThemeOnDocument

Events:

  • on(eventName, handler)
  • onRegistered
  • onThemeChanged
  • onVariantAdded
  • onLoaded
  • onComposed
  • onBrandViolation
  • onDestroyed

Brand Governance Toolkit

This package supports three governance layers.

Phase 1: Contract Strictness at Registration

Define a BrandContract and enforce it during register and registerPlugin calls.

import { themeRegistry, type BrandContract } from '@12nil/theme-registry-package'

const contract: BrandContract = {
  name: 'Acme Brand v1',
  defaultLevel: 'error',
  colors: {
    requireCssVariable: true,
    allowedByToken: {
      'colors.primary': ['var(--brand-primary)'],
      primary: ['var(--brand-primary)'],
    },
  },
  fonts: {
    allowedValues: ['Inter, sans-serif', 'Merriweather, serif'],
  },
  spacing: {
    allowedValues: ['4px', '8px', '12px', '16px', '24px', '32px'],
  },
  borderRadius: {
    allowedValues: ['0', '4px', '8px', '12px', '9999px'],
  },
}

themeRegistry.setBrandContract(contract, { enforce: true })

Severity behavior:

  • error: throws and blocks registration
  • warn: logs warning and allows registration
  • off: skips the rule

Optional per-call override:

themeRegistry.register(theme, { enforceBrand: true })
themeRegistry.registerPlugin(plugin, { enforceBrand: true })

Phase 2: Runtime Guardrails

Enable runtime checks in development to catch drift during load and theme application.

themeRegistry.setBrandRuntimeGuard({
  enabled: true,
  devOnly: true,
  treatWarningsAsErrors: false,
  onViolation: (payload) => {
    console.log(payload.context, payload.issues)
  },
})

const unsubscribe = themeRegistry.onBrandViolation((payload) => {
  console.log('brand violation', payload)
})

// Call unsubscribe() when you no longer need the listener.

Runtime guard checks run during:

  • load
  • compose
  • setCurrent
  • hydrateThemeOnDocument

Phase 3: CI Audit

Use the CLI to scan JSON themes and source files against the same contract.

npx runtime-theme-registry audit-brand ./brand-contract.json ./themes

ESLint Rule for Off-Brand Values

This package includes an ESLint rule that reports off-brand values in:

  • style object literals (for example color, backgroundColor, spacing, borderRadius, fontFamily)
  • className arbitrary color syntax (for example bg-[#ff0000])

Rule id:

  • runtime-theme-registry/brand-contract
  • runtime-theme-registry/tailwind-brand-classes

Flat config example (eslint.config.js):

import runtimeThemeRegistryEslint from '@12nil/theme-registry-package/eslint'

export default [
  {
    files: ['**/*.{js,jsx,ts,tsx}'],
    plugins: {
      'runtime-theme-registry': runtimeThemeRegistryEslint,
    },
    rules: {
      'runtime-theme-registry/brand-contract': ['error', {
        contractPath: './brand-contract.json',
      }],
      'runtime-theme-registry/tailwind-brand-classes': ['error', {
        contractPath: './brand-contract.json',
      }],
    },
  },
]

Rule options:

  • contractPath path to BrandContract JSON (default ./brand-contract.json)
  • rootDir base path for resolving contractPath (default process cwd)

tailwind-brand-classes options:

  • allowedClasses extra Tailwind color utilities to allow
  • allowArbitraryValues allow classes like bg-[#ff0000] (default false)

The tailwind-brand-classes rule enforces allowlisted color utility usage such as:

  • bg-primary, text-muted, border-border-subtle
  • fill-primary, stroke-info

and reports non-approved classes such as:

  • bg-red-500
  • text-blue-600
  • border-emerald-400

Severity behavior:

  • ESLint controls final severity (error or warn) based on your rule config.
  • Contract warn and error levels are preserved in the message prefix for context.
  • Contract off disables checks for that rule.

CLI

Inject utility classes into CSS

npx runtime-theme-registry inject-css ./src/app/globals.css

This adds or updates a marked utility block.

Audit brand compliance

npx runtime-theme-registry audit-brand ./brand-contract.json ./themes

Flags:

  • --format json|text (default text)
  • --fail-on error|warn (default error)
  • --source-path
  • --no-source-scan
  • --source-scope all|strict (default all)
  • --source-include

Strict source scope defaults:

  • src
  • app
  • components
  • pages
  • styles

Examples:

# Fail only on error-level issues.
npx runtime-theme-registry audit-brand ./brand-contract.json ./themes --format text --fail-on error

# Fail on warnings too, output JSON for CI parsers.
npx runtime-theme-registry audit-brand ./brand-contract.json ./themes --format json --fail-on warn

# Audit theme JSON under ./themes but scan source files in ./src.
npx runtime-theme-registry audit-brand ./brand-contract.json ./themes --source-path ./src

# Strict source scope using defaults.
npx runtime-theme-registry audit-brand ./brand-contract.json . --source-scope strict

# Strict source scope with explicit roots.
npx runtime-theme-registry audit-brand ./brand-contract.json . --source-scope strict --source-include src,app,components

Audit summary fields:

  • filesScanned
  • sourceFilesScanned
  • themesScanned
  • errorCount
  • warningCount
  • sourceScope
  • sourceRoots
  • status

Tailwind v4 Integration

ThemeProvider can inject utility classes at runtime.

If you prefer CSS-first mapping in global styles:

@import "tailwindcss";

@theme inline {
  --color-primary: var(--theme-primary);
  --color-secondary: var(--theme-secondary);
  --color-background: var(--theme-background);
  --color-text: var(--theme-text);
  --color-accent: var(--theme-accent);
  --color-muted: var(--theme-muted);
  --color-error: var(--theme-error);
  --color-warning: var(--theme-warning);
  --color-success: var(--theme-success);
  --color-info: var(--theme-info);
  --color-surface-card: var(--surface-card);
  --color-border-subtle: var(--border-subtle);
}

Use mapped utilities:

<button className="bg-primary text-background border border-border-subtle">Save</button>
<p className="text-muted">Secondary text</p>
<div className="bg-surface-card">Card</div>

If you inject utility classes at build-time via CLI, disable runtime injection:

<ThemeProvider injectUtilityClasses={false}>
  <App />
</ThemeProvider>

Advanced Workflows

Plugin registration and contributions

import { themeRegistry } from '@12nil/theme-registry-package'

themeRegistry.registerPlugin({
  name: 'CRM',
  version: '1.0.0',
  themes: [
    {
      name: 'crm',
      modes: {
        light: {
          primary: '#1d4ed8',
          secondary: '#2563eb',
          background: '#ffffff',
          text: '#0f172a',
          accent: '#1d4ed8',
          muted: '#64748b',
          error: '#dc2626',
          warning: '#f59e0b',
          success: '#16a34a',
          info: '#0ea5e9',
        },
      },
    },
  ],
  fonts: {
    body: 'Inter, sans-serif',
  },
  spacing: {
    md: '16px',
  },
  radii: {
    card: '12px',
  },
})

const merged = themeRegistry.getMergedPluginContributions()
console.log(merged.fonts)

Composition and loading

import { themeRegistry } from '@12nil/theme-registry-package'

themeRegistry.registerCompositionLayer({
  type: 'brand',
  name: 'fnp',
  tokens: {
    primary: '#031011',
    secondary: '#153e46',
    background: '#ffffff',
    text: '#031011',
    accent: '#378d93',
    muted: '#667085',
    error: '#DF1C41',
    warning: '#F4C790',
    success: '#27AE60',
    info: '#378d93',
  },
})

themeRegistry.registerCompositionLayer({
  type: 'appearance',
  name: 'dark',
  tokens: {
    background: '#031011',
    text: '#c4e8ee',
    secondary: '#378d93',
    primary: '#c4e8ee',
    accent: '#378d93',
    muted: '#667085',
    error: '#DF1C41',
    warning: '#F4C790',
    success: '#27AE60',
    info: '#378d93',
  },
})

const composed = themeRegistry.compose({ brand: 'fnp', appearance: 'dark' })

await themeRegistry.load('/api/themes', {
  ifExists: 'merge',
  cacheKey: 'customer-themes',
  maxAgeMs: 5 * 60 * 1000,
})

const attrs = themeRegistry.getInitialThemeAttributes('fnp', 'dark')
const hydrated = themeRegistry.hydrateThemeOnDocument('fnp', 'dark')

themeRegistry.clearLoadCache('customer-themes')

API Overview

Main exports include:

  • ThemeProvider, useTheme
  • ThemeSwitcher, ThemeSwitcherStyled
  • themeRegistry
  • validateTheme, isThemeTokensV2
  • validateThemeAgainstBrand, validatePluginAgainstBrand
  • paletteToCSSVariables, tokenSetToCSSVariables
  • createTailwindThemeColorMap
  • generateThemeCSS, generateAllThemesCSS, applyThemeToDocument
  • Comprehensive types for themes, plugins, governance, composition, events, and loading

Development

npm.cmd install
npm.cmd run build

Publish checklist:

  1. Update package name/version in package.json.
  2. Run npm.cmd run build.
  3. Run npm.cmd pack --dry-run.
  4. Publish with npm.cmd publish --access public.

Related Docs