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

@jslibkit/tailwind-theme-engine

v2.1.0

Published

Dynamic runtime theming for Tailwind CSS using semantic CSS variable color scales.

Readme

tailwind-theme-engine

Switch your whole app's colors at runtime — with plain Tailwind classes.

<div class="bg-primary-500 text-primary-100">Hello</div>

You write bg-primary-500 once. The engine decides what "primary" means right now: purple theme, green theme, light mode, dark mode. Switching is instant. Tailwind never rebuilds.

What you get

  • Multiple themes from one small config file (purple, green, your brand — anything).
  • A full 50–950 shade scale generated automatically from ONE base color per name.
  • Runtime switching — change theme or dark mode with one function call.
  • Dark mode for free — the shade scale flips (50 ↔ 950), your classes stay the same.
  • System mode — follow the visitor's OS light/dark setting, live.
  • No flash on refresh — a tiny generated script restores the visitor's choice before the page paints.
  • Custom color names — call them primary, accent, surface, whatever. They all become real Tailwind classes.
  • TypeScript types included.

Requirements

  • Node.js 18 or newer
  • Tailwind CSS 3 or newer (v4 recommended)
  • React 18 or 19 (only if you use the React hook — the core works without React)
  • Your app uses ESM / a bundler (Vite, Next.js, etc. — all fine)

Install in a NEW project (React + Tailwind v4)

Follow these steps in order. Don't skip any.

Step 1 — Install the package

Open a terminal in your project folder and run ONE of these:

npm install @jslibkit/tailwind-theme-engine
# or
bun add @jslibkit/tailwind-theme-engine
# or
pnpm add @jslibkit/tailwind-theme-engine

Step 2 — Create the config file

npx tte init
  • This creates a file called tailwind-theme-engine.config.js in your project root.
  • If the file already exists, nothing is touched (use npx tte init --force to reset it).

Step 3 — Open the config and set your colors

Open tailwind-theme-engine.config.js. It looks like this:

export const storageKey = "app-theme";          // where the chosen theme is saved
export const modeStorageKey = "app-color-mode"; // where light/dark is saved

export const themeDefinitions = {
  purple: {
    label: "Purple",
    colors: {
      primary: "#8b5cf6",
      secondary: "#ec4899",
      tertiary: "#111827",
      success: "#22c55e",
      warning: "#f59e0b",
      info: "#0ea5e9",
      danger: "#ef4444"
    }
  },
  green: {
    label: "Green",
    colors: { /* ... */ }
  }
};

Rules:

  • Each key under themeDefinitions (like purple) is a theme name.
  • Each key under colors (like primary) becomes Tailwind classes (bg-primary-500, text-primary-50, ...).
  • You give ONE color per name — the engine generates all 11 shades from it.
  • The 500 shade is EXACTLY the color you typed.
  • Any CSS color works: #8b5cf6, rgb(139 92 246), hsl(258 90% 66%), oklch(0.6 0.2 290), even rebeccapurple.
  • Add or rename colors freely (accent, surface, ...). Just use the same names in every theme.

Step 4 — Generate the theme files

npx tte build

This creates a theme/ folder:

theme/
  purple.css          ← CSS variables for the purple theme
  purple.js           ← same data as a JS module
  green.css
  green.js
  index.js            ← exports: themes, themeOptions, storageKey, modeStorageKey
  bridge.css          ← registers your Tailwind classes (v4)
  tailwind-preset.js  ← same thing for Tailwind v3
  no-flash.js         ← anti-flash script for your <head>
  • Run npx tte build again ANY time you change the config. It overwrites its own files automatically.
  • Want it somewhere else? npx tte build --out-dir src/theme

Step 5 — Wire up your CSS

Open your main CSS file (the one with @import "tailwindcss") and make it:

@import "tailwindcss";
@import "./theme/bridge.css";
@import "./theme/purple.css";
  • Line 2 registers the Tailwind classes for YOUR color names.
  • Line 3 picks which theme paints FIRST (before any JavaScript runs). Pick your default.
  • Adjust the ./theme/ path to wherever the folder actually is, relative to this CSS file.

Step 6 — Add the no-flash script

  • Copy theme/no-flash.js into your static folder (in Vite that's public/).
  • Open index.html and add this line inside <head>, near the top:
<head>
  <script src="/no-flash.js"></script>
  ...
</head>

Important:

  • It must be a plain <script> — do NOT add type="module" (modules run too late and you'll get a flash).
  • Re-copy the file after every npx tte build (or point a build step at it).
  • What it does: if the visitor picked "green + dark" last time, the page paints green + dark immediately instead of flashing the default first.

Step 7 — Initialize the engine (once)

Open your entry file (main.jsx / main.tsx) and add the init call BEFORE rendering:

import React from "react";
import ReactDOM from "react-dom/client";
import { initializeThemeEngine } from "@jslibkit/tailwind-theme-engine/react";
import { themes, storageKey, modeStorageKey } from "./theme";
import "./index.css";
import App from "./App";

initializeThemeEngine({
  initialTheme: "purple",   // must match a theme name from your config
  themes,
  storageKey,
  modeStorageKey
});

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
  • themes, storageKey, modeStorageKey all come from the generated theme/index.js — you don't type them twice.
  • No provider component is needed. Ever.

Step 8 — Use it in any component

import { useThemeEngine } from "@jslibkit/tailwind-theme-engine/react";

export default function App() {
  const { themeName, setTheme, resolvedColorMode, toggleColorMode } = useThemeEngine();

  return (
    <main className="min-h-screen bg-primary-50 text-primary-950 p-8">
      <button
        className="bg-primary-500 hover:bg-primary-600 text-white rounded-md px-4 py-2"
        onClick={() => setTheme(themeName === "purple" ? "green" : "purple")}
      >
        Switch theme
      </button>

      <button
        className="bg-tertiary-200 text-tertiary-900 rounded-md px-4 py-2"
        onClick={toggleColorMode}
      >
        {resolvedColorMode === "dark" ? "☀️ Light" : "🌙 Dark"}
      </button>
    </main>
  );
}

Step 9 — Run it

npm run dev

Check that:

  • [ ] Colors show up (not black/transparent).
  • [ ] Clicking "Switch theme" changes colors instantly.
  • [ ] Clicking the dark toggle flips the page.
  • [ ] After picking green + dark, a page refresh does NOT flash purple/light first.

Done. That's the whole setup.


Install with Tailwind v3 instead

Everything above is the same EXCEPT steps 5. Do this instead:

  • Step 5a — Open (or create) tailwind.config.js and add the generated preset:
import preset from "./theme/tailwind-preset.js";

export default {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  presets: [preset]
};
  • Step 5b — In your main CSS file, import one theme (no bridge.css needed on v3):
@import "./theme/purple.css";

All other steps (1–4, 6–9) are identical.


Everyday workflow (after setup)

Want to change a color or add a theme?

  1. Edit tailwind-theme-engine.config.js.
  2. Run npx tte build.
  3. Re-copy theme/no-flash.js to public/ if you changed themes/colors.
  4. Refresh the browser.

That's it. No Tailwind restart tricks, no other files to touch.

Adding a brand-new color name (say accent)?

  1. Add accent: "#f97316" to the colors of EVERY theme in the config.
  2. Run npx tte build.
  3. Use bg-accent-500 etc. in your code.

Dark mode in 30 seconds

  • setColorMode("dark") — force dark. The shade scale flips (50 ↔ 950, 100 ↔ 900...), so all your existing classes just render dark.
  • setColorMode("light") — force light.
  • setColorMode("system") — follow the visitor's OS setting, and update LIVE when the OS switches.
  • toggleColorMode() — flip to the opposite of whatever is showing right now.
  • colorMode — what the user chose: "light" | "dark" | "system".
  • resolvedColorMode — what's actually on screen: "light" | "dark". Use THIS for your sun/moon icon.

Design tip: dark mode looks best when your light shades are actually light and your dark shades actually dark. Look at each theme in both modes once.


UPGRADE GUIDE

Upgrading from v1.x → v2.1

v2 is a cleanup release. Follow every step, in order:

  1. Update the package:

    npm install @jslibkit/tailwind-theme-engine@latest
  2. Check your config file exists. v1 tried to create it automatically during install; that's gone (it silently failed on Bun/pnpm anyway). If you don't have tailwind-theme-engine.config.js in the project root:

    npx tte init
  3. Add the storage keys to your config (new, optional but recommended). Open the config and add at the top:

    export const storageKey = "app-theme";
    export const modeStorageKey = "app-color-mode";

    ⚠️ If your app ALREADY passes a storageKey to initializeThemeEngine or useThemeEngine, use that SAME string here — otherwise visitors lose their saved choice.

  4. Rebuild your themes:

    npx tte build

    Notes:

    • You do NOT need --force anymore. Generated files are overwritten automatically.
    • Shade values will change slightly — the 500 shade is now EXACTLY your base color (v1 was a close approximation). This is a visual improvement, not a bug.
  5. Switch to the generated bridge/preset so custom color names work:

    • Tailwind v4 — in your CSS, replace the old package import with the generated one:

      /* before */
      @import "@jslibkit/tailwind-theme-engine/theme.css";
      /* after */
      @import "./theme/bridge.css";
    • Tailwind v3 — in tailwind.config.js, replace the old preset:

      // before
      import preset from "@jslibkit/tailwind-theme-engine/preset";
      // after
      import preset from "./theme/tailwind-preset.js";

    (If you only use the 7 standard names, the old imports still work — but the generated ones are better because they follow your config.)

  6. Add the no-flash script (new feature — fixes theme flash on refresh):

    • Copy theme/no-flash.js to your public/ folder.
    • Add <script src="/no-flash.js"></script> near the top of <head> in index.html.
    • Plain script tag only — no type="module".
  7. Update your init call to pull the keys from the generated folder:

    import { themes, storageKey, modeStorageKey } from "./theme";
    
    initializeThemeEngine({ initialTheme: "purple", themes, storageKey, modeStorageKey });
  8. Check one behavior change in the hook: in v1, useThemeEngine({ ...options }) re-applied its options on every render (this could cause infinite loops). In v2, hook options are used ONCE, only if nothing initialized the engine yet.

    • If you relied on changing hook options at runtime: call initializeThemeEngine(newOptions) explicitly instead.
    • If you just passed options once at the root: nothing to do.
  9. Delete the leftover v1 file (if it's still in your repo clone of this library): src/cli/postinstall.js is unused in v2.

  10. Run your app and verify the checklist from Step 9 of the install guide.

Upgrading from v2.0 → v2.1

Much smaller. In order:

  1. Update:

    npm install @jslibkit/tailwind-theme-engine@latest
  2. Add storage keys to the config (see step 3 above) — optional, but the no-flash script can't restore anything without them.

  3. Rebuild:

    npx tte build

    ⚠️ Do this in EVERY project using the package. 2.1 anchors the 500 shade to your exact base color, so generated CSS/JS values shift slightly. Rebuilding keeps everything consistent.

  4. Add the no-flash script to public/ + index.html <head> (see step 6 above).

  5. Optional new toys:

    • setColorMode("system") — follow the OS, live.
    • resolvedColorMode — what's actually showing (for icons).
    • import { storageKey, modeStorageKey } from "./theme" — single source of truth.

No other code changes are required. v2.0 code runs unchanged on v2.1.


Command cheat sheet

| Command | What it does | | --- | --- | | npx tte init | Create the config file (never overwrites without --force). | | npx tte build | Generate/refresh everything in theme/. | | npx tte build --out-dir src/theme | Generate somewhere else. | | npx tte build --config other.config.js | Use a different config file. | | npx tte build --force | Also overwrite files YOU edited by hand in the output folder. | | npx tte help | Show help. |


Troubleshooting

  • My colors don't show at all (everything transparent/black).

    • v4: you forgot @import "./theme/bridge.css"; in your CSS.
    • v3: you forgot the preset in tailwind.config.js.
    • Both: you forgot to import one theme CSS file (e.g. ./theme/purple.css).
  • Classes for my custom color (e.g. bg-accent-500) don't work.

    • Run npx tte build after adding the color to the config.
    • Make sure you import the GENERATED bridge.css / tailwind-preset.js, not the package's static ones (those only know the 7 standard names).
  • The page flashes the default theme on refresh.

    • The no-flash script is missing, in the wrong place, or loaded with type="module". See install Step 6.
    • The script and your app must use the SAME storage keys. Export the keys from the config and import them from ./theme everywhere.
  • Error: Invalid color value: ... (theme "x", color "y").

    • The message tells you exactly which theme and color is broken. Usually a typo like #8b5cf (5 digits).
  • Error: Unknown theme: "x". Available themes: ....

    • You called setTheme with a name that isn't in your config. The error lists the valid names.
  • Error: Refusing to overwrite <file>.

    • You hand-edited a file inside the output folder. Either move your custom code out of theme/, or run npx tte build --force to regenerate it.
  • Dynamic class names don't work: bg-${color}-500.

    • That's a Tailwind rule, not ours: class names must be static strings so Tailwind can see them at build time. Write the full class name.
  • You passed the wrong thing to the engine.

    • Generated scales → initializeThemeEngine({ themes }).
    • Raw base colors → initializeThemeEngine({ themeDefinitions }).
    • Mixing them up throws a color-validation error.

API quick reference

React (@jslibkit/tailwind-theme-engine/react):

  • initializeThemeEngine(options) — set up once before render. Options: initialTheme, themes, themeDefinitions, includeDefaultThemes, initialColorMode, root, storageKey, modeStorageKey.
  • useThemeEngine() — hook; returns the state below.
  • getThemeEngine() / setTheme(name) / setColorMode(mode) / toggleColorMode() — same things outside React.
  • isThemeEngineInitialized() — boolean.

State: themeName, theme, themes, colorMode ("light" | "dark" | "system"), resolvedColorMode ("light" | "dark"), setTheme, setThemeName, setColorMode, toggleColorMode.

Core (@jslibkit/tailwind-theme-engine):

  • createTheme({ colors, name? }) — base colors → full shade scales.
  • generateScale(color) — one color → one 50–950 scale.
  • applyTheme(theme, root?, colorMode?) — write CSS variables to the DOM.
  • resolveColorMode(mode) — "system" → "light" | "dark".
  • createPreset(colorNames?) — build a Tailwind v3 preset for any names (from /preset).
  • Plus: SHADE_STEPS, SEMANTIC_COLOR_NAMES, createThemeCollection, extendThemeDefinitions, defaultThemeDefinitions, defaultThemes, getDefaultThemes.

TypeScript: Theme, ThemeDefinitions, ColorMode, ResolvedColorMode, ThemeEngineState, ThemeEngineOptions and more are exported from the package root and /react.


Contributing / development

git clone <your-repo-url>
cd tailwind-theme-engine
npm install
npm run build   # syntax check
npm test        # run the test suite

Project layout:

src/core       color parsing, scale generation, theme creation
src/runtime    CSS variable application (light/dark/system)
src/react      shared store + React hook
src/tailwind   Tailwind preset
src/themes     built-in themes and palettes
src/cli        init / build / generate commands
test/          node:test suites

See UPGRADING.md for the full version-by-version upgrade notes.

License

MIT