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 🙏

© 2025 – Pkg Stats / Ryan Hefner

tweakcn-ui

v0.5.8

Published

Reusable Theme Editor UI kit extracted from tweakcn.com. Import ThemeEditor or individual parts.

Downloads

415

Readme

tweakcn-ui

Reusable UI kit extracted from tweakcn.com. It lets you:

  • Import a single ThemeEditor component that behaves like tweakcn’s editor (without auth/AI/save).
  • Import individual sub-components (ColorPicker, SliderWithInput, FontPicker, ThemePreviewPanel, etc.) to build your own custom UI.

Install

Add the package to your app. Supports React 18 and 19.

Peer deps: react@>=18 <20, react-dom@>=18 <20. TypeScript: install @types/react that matches your React version.

Runtime deps used by the package: zustand, lucide-react, clsx, tailwind-merge, culori, zod.

Changes in 0.4.3

  • Split package into server-safe and client-only entry points to support Next.js RSC without runtime errors.
    • Root tweakcn-ui now exports only RSC-safe utilities and types.
    • Added tweakcn-ui/client for client components and hooks (ThemeEditor, ThemeProvider, ThemeRoot, ThemeScript, Previews, etc.).
    • Added tweakcn-ui/server for server components (ServerThemePayload).
  • Fixes errors like (0, react__WEBPACK_IMPORTED_MODULE_0__.createContext) is not a function when importing the library from Server Components.
  • Updated documentation with RSC-safe usage examples and a migration guide.

Usage (Next.js / RSC)

This package provides separate entry points to avoid RSC importing client code:

  • tweakcn-ui (root): RSC-safe utilities only
  • tweakcn-ui/client: client components and hooks
  • tweakcn-ui/server: server-only components

Wrap your app (or the area you edit) with ThemeProvider and include ThemeScript in your HTML <head> to hydrate tokens early. Import these from the client entry.

"use client";
import { ThemeProvider, ThemeScript, ThemeEditor } from 'tweakcn-ui/client';

export default function Page() {
  return (
    <html>
      <head>
        <ThemeScript />
      </head>
      <body>
        <ThemeProvider>
          <div style={{ height: '80vh' }}>
            <ThemeEditor
              googleFontsApiKey={process.env.NEXT_PUBLIC_GOOGLE_FONTS_API_KEY}
            />
          </div>
        </ThemeProvider>
      </body>
    </html>
  );
}

ThemeScript and ThemeProvider now hydrate font families alongside CSS variables. Use the optional skipGoogleFonts prop when your app already imports certain families (for example via next/font) and fontTargets to override which selectors get each role (sans, serif, mono).

If you prefer to compose your own UI:

"use client";
import { ThemeProvider, ThemeControlPanel, ThemePreviewPanel, useEditorStore } from 'tweakcn-ui/client';

function CustomBuilder() {
  const { themeState, setThemeState } = useEditorStore();
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
      <ThemeControlPanel
        styles={themeState.styles}
        currentMode={themeState.currentMode}
        onChange={(styles) => setThemeState({ ...themeState, styles })}
        themePromise={Promise.resolve(null)}
      />
      <ThemePreviewPanel styles={themeState.styles} currentMode={themeState.currentMode} />
    </div>
  );
}

New: Theme payload viewers (server + client)

  • ServerThemePayload (server component) prints any ThemeStatePayload in layouts or server-rendered sections.
  • ClientThemePayload (client component) shows a live-updating payload from the editor store.

Server example (Next.js layout):

// app/layout.tsx (server component)
import { ServerThemePayload } from 'tweakcn-ui/server';
import ThemeRootClient from './ThemeRootClient';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const payload = await loadThemeFromDBOrFile(); // returns ThemeStatePayload
  return (
    <html lang="en">
      <head>{/* render ThemeScript in ThemeRootClient */}</head>
      <body>
        <ThemeRootClient>
          <ServerThemePayload payload={payload} />
          {children}
        </ThemeRootClient>
      </body>
    </html>
  );
}

Client example (live changes):

Client shim used above:

// app/ThemeRootClient.tsx
"use client";
import { ThemeRoot, ThemeScript } from 'tweakcn-ui/client';
export default function ThemeRootClient({ children }: { children: React.ReactNode }) {
  return (
    <ThemeRoot skipGoogleFonts={["Inter"]}>
      <ThemeScript skipGoogleFonts={["Inter"]} />
      {children}
    </ThemeRoot>
  );
}

Adjust skipGoogleFonts/fontTargets to match the fonts your app already handles or to retarget typography roles.

"use client";
import { ClientThemePayload } from 'tweakcn-ui/client';

export default function DebugPanel() {
  return <ClientThemePayload title="Live Theme Payload" />;
}

The payload now includes a fonts array describing which families are active, whether they map to next/font, and the Google import URLs the runtime will load.

Export/Import theme JSON

  • Export the current theme state as JSON you can persist:
import { exportThemePayload } from 'tweakcn-ui';
const payload = exportThemePayload(); // plain JSON: styles + fonts + currentMode + hslAdjustments + preset
localStorage.setItem('my-theme', JSON.stringify(payload));
  • Import a previously saved payload and apply live:
import { importThemePayload, parseThemePayload } from 'tweakcn-ui';
const raw = localStorage.getItem('my-theme');
if (raw) {
  const payload = parseThemePayload(raw); // validates structure
  importThemePayload(payload);            // updates store and applies CSS vars on :root
}

Apply or compute CSS variables

  • Apply the theme in store to document.documentElement:
import { applyThemeVars } from 'tweakcn-ui';
applyThemeVars();
  • Compute a map of CSS variables without mutating the DOM:
import { computeCssVarMap } from 'tweakcn-ui';
// in client code
import { useEditorStore } from 'tweakcn-ui/client';
const state = useEditorStore.getState().themeState;
const vars = computeCssVarMap(state); // { --background: 'hsl(...)', --radius: '...', --shadow-md: '...' }

Persist to your DB with hooks

import { useThemePersistence } from 'tweakcn-ui/client';

function Settings() {
  const { save, load, loading } = useThemePersistence({
    load: async () => {
      const r = await fetch('/api/theme');
      return await r.json();
    },
    save: async (payload) => {
      await fetch('/api/theme', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) });
    },
  });
  return (
    <div>
      <button onClick={() => load()} disabled={loading}>Load</button>
      <button onClick={() => save()} disabled={loading}>Save</button>
    </div>
  );
}

Richer previews (optional)

Import optional previews for Cards, Pricing, and Shadows without extra deps:

import { Previews } from 'tweakcn-ui/client';

function Showcase() {
  return (
    <div className="space-y-6 p-4">
      <Previews.CardsPreview />
      <Previews.PricingPreview />
      <Previews.ShadowsPreview />
    </div>
  );
}

You can also enable them inside ThemePreviewPanel via its tabs.

Entry Points

  • tweakcn-ui (root, RSC-safe)
    • exportThemePayload, importThemePayload, parseThemePayload
    • applyThemeVars, computeCssVarMap
    • Types (ThemeStatePayload, ThemeStyles, ...)
  • tweakcn-ui/server
    • ServerThemePayload
  • tweakcn-ui/client
    • ThemeEditor, ThemeProvider, useTheme, ThemeRoot, ThemeScript
    • UI building blocks: ThemeControlPanel, ThemePreviewPanel, ColorPicker, SliderWithInput, FontPicker, HslAdjustmentControls
    • useEditorStore, ClientThemePayload, Previews

Migration (>= 0.4.3)

If you previously imported client components from the root entry (e.g., import { ThemeEditor } from 'tweakcn-ui') and saw errors like (0, react__WEBPACK_IMPORTED_MODULE_0__.createContext) is not a function, switch those imports to the client entry:

  • Before: import { ThemeEditor, ThemeProvider, ThemeRoot, ThemeScript } from 'tweakcn-ui'
  • After: import { ThemeEditor, ThemeProvider, ThemeRoot, ThemeScript } from 'tweakcn-ui/client'

And import server-only pieces from tweakcn-ui/server (e.g., ServerThemePayload).

API: Exports overview

  • Core

    • ThemeEditor (client) � full editor UI
    • ThemeProvider (client) � applies/updates CSS vars, loads fonts, and manages theme mode (skipGoogleFonts / fontTargets)
    • ThemeRoot (client) � thin wrapper around ThemeProvider; forwards font options downstream
    • ThemeScript (client) � early CSS var + font hydration script (put in <head>)
  • Theme payload viewers

    • ServerThemePayload (server) – pretty-prints a provided ThemeStatePayload
    • ClientThemePayload (client) – live payload from the store
  • Building blocks

    • ThemeControlPanel (client)
    • ThemePreviewPanel (client)
    • ColorPicker (client)
    • SliderWithInput (client)
    • FontPicker (client)
    • HslAdjustmentControls (client)
  • Store and hooks

    • useEditorStore – Zustand store with themeState
    • useTheme – provider context with theme/setTheme/toggleTheme
    • useThemeExport, useThemeImport, useThemePersistence – helpers for IO

    Note: the store and hooks above are client-only; import them from tweakcn-ui/client.

  • Serialization/runtime

    • exportThemePayload, importThemePayload, parseThemePayload
    • applyThemeVars, computeCssVarMap
  • Previews namespace

    • Previews.CardsPreview, Previews.PricingPreview, Previews.ShadowsPreview
  • Types

    • Re-exported from ./types (e.g. ThemeStatePayload, ThemeStyles, etc.)

Google Fonts integration

  • The bundled font selector pulls families from Google Fonts. Provide your key anywhere you render the editor UI:
    • <ThemeEditor googleFontsApiKey={process.env.NEXT_PUBLIC_GOOGLE_FONTS_API_KEY} />
    • <ThemeControlPanel googleFontsApiKey={process.env.NEXT_PUBLIC_GOOGLE_FONTS_API_KEY} />
    • <FontPicker googleApiKey={process.env.NEXT_PUBLIC_GOOGLE_FONTS_API_KEY} />
  • Without a key, the editor falls back to the built-in shortlist so the controls keep working, just without search over the full catalog.
  • You can disable the Google Web Fonts fetch (for example if you generate your list server-side) via useGoogleWebFontsApi={false} on FontPicker.
  • ThemeScript, ThemeProvider, and ThemeRoot automatically load Google CSS for any non-system font selected in the theme. Supply skipGoogleFonts={["Inter", "Roboto"]} (or similar) when your app already imports those families via next/font or self-hosting:
<ThemeRoot skipGoogleFonts={["Inter"]}>
  <ThemeScript skipGoogleFonts={["Inter"]} />
</ThemeRoot>
  • Override the default typography targets (headings -> serif, body -> sans, etc.) with fontTargets, e.g. { serif: ["h1", "h2"], sans: ["body", ".prose p"] }.

  • The current font payload is stored in localStorage alongside CSS vars and exposed on window.__tweakcnSelectedFonts for debugging.

  • To inspect the derived font import URLs and flags, render the debug payload component:

import { ClientFontsPayload } from 'tweakcn-ui/client';
<ClientFontsPayload />

Build & publish

cd tweakcn-ui
npm install  # installs dev deps (tsup, typescript)
npm run build
npm publish --access public

Styling + Tailwind

This kit uses Tailwind-style tokens (e.g. bg-background, text-foreground, etc.). Include Tailwind v4 or set CSS variables yourself. ThemeScript/ThemeProvider apply CSS vars on :root.

shadcn/ui + Radix

The internal UI primitives are implemented using shadcn/ui patterns powered by Radix UI and cmdk:

  • Buttons, Cards, Inputs, Labels, Tabs, Popover, Tooltip, Select, Switch, Slider, Separator, ScrollArea, Resizable, Command palette.

Notes for consumers:

  • No CLI needed in your app; the components are already included in this package.
  • Ensure Tailwind is configured (tokens like bg-background, text-foreground, border, etc.).
  • Optional: if you want shadcn-style enter/exit animations, add the tailwindcss-animate plugin to your Tailwind config. Without it, components still work but without those transitions.
  • If your Tailwind build does not scan node_modules, consider safelisting common utility classes used by these components (optional). Example:
// tailwind.config.js (example)
module.exports = {
  content: [
    './src/**/*.{ts,tsx}',
    // optionally include the library if your setup purges aggressively
    // './node_modules/tweakcn-ui/dist/**/*.{js,cjs}',
  ],
  plugins: [require('tailwindcss-animate')], // optional
  safelist: [
    { pattern: /(w-\[\d+rem\]|h-\d+|p-\d+|px-\d+|py-\d+|rounded-md|rounded-lg)/ },
    { pattern: /(animate-in|fade-in-0|zoom-in-95|slide-in-from-(top|bottom|left|right)-2)/ },
    { pattern: /(data-\[state=(open|closed|active|inactive)\]:.*)/ },
  ],
};

Because this is a library, the UI classes are bundled in JS. Most apps already use a superset of these utilities, so safelisting is often unnecessary, but it can help in strict purge setups.

Notes

  • Trimmed features: Auth, AI, save/share, Next.js-only pieces. You can build those on top using the exported stores and callbacks.
  • Preview includes Typography + Color Palette by default to avoid heavy demo deps. You can add your own previews.

Attribution

Portions of this code are based on tweakcn (https://github.com/jnsahaj/tweakcn) licensed under Apache-2.0.

Simplest Setup

  • Next/React app using Tailwind v4:
"use client";
import { ThemeRoot, ThemeScript, ThemeEditor } from 'tweakcn-ui/client';

export default function Page() {
  return (
    <html>
      <head>
        <ThemeScript />
      </head>
      <body>
          <ThemeRoot>
            <ThemeEditor googleFontsApiKey={process.env.NEXT_PUBLIC_GOOGLE_FONTS_API_KEY} />
          </ThemeRoot>
      </body>
    </html>
  );
}
  • Tailwind v3 (expects raw HSL in CSS vars):
"use client";
import { ThemeRoot, ThemeScript, ThemeEditor } from 'tweakcn-ui/client';

export default function Page() {
  return (
    <>
      <ThemeScript />
        <ThemeRoot tailwindVersion="3">
          <ThemeEditor googleFontsApiKey={process.env.NEXT_PUBLIC_GOOGLE_FONTS_API_KEY} />
        </ThemeRoot>
    </>
  );
}

Notes:

  • ThemeRoot wraps ThemeProvider and applies theme vars to :root.
  • It also persists a precomputed var map and the current font payload to localStorage under tweakcn-css-vars for faster early hydration used by ThemeScript.
  • All internal buttons default to type="button" to avoid form submits in admin UIs.
  • Supplying googleFontsApiKey on ThemeEditor unlocks the full Google Fonts catalog; omit it only if the fallback list covers your needs or you provide your own picker.

Persisted themes and fonts

If you store a ThemeStatePayload per tenant, pass it to both ThemeScript and ThemeRoot so fonts persist across sessions without a flash of defaults:

import { ThemeRoot, ThemeScript } from "tweakcn-ui/client";

const payload = await loadTenantTheme(tenant); // ThemeStatePayload | null

<html lang="en">
  <head>
    <ThemeScript
      initialPayload={payload}
      localStorageKey={`tweakcn-css-vars:${tenant}`}
      skipGoogleFonts={SKIP_GOOGLE_FONTS}
    />
  </head>
  <body>
    <ThemeRoot
      initialThemePayload={payload}
      localStorageKey={`tweakcn-css-vars:${tenant}`}
      skipGoogleFonts={SKIP_GOOGLE_FONTS}
    >
      {children}
    </ThemeRoot>
  </body>
</html>

ThemeScript writes the payload into the persisted editor store (editor-storage) before hydration and primes Google fonts. ThemeRoot mirrors the same data into the Zustand store without polluting history so the UI rehydrates with the exact same fonts and CSS variables.