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

theme-o-rama

v0.4.0

Published

A TypeScript library for dynamic theme management in react + shadcn + tailwind applications

Readme

Theme-o-rama

A comprehensive TypeScript library for dynamic theme management in React applications with shadcn/ui and Tailwind CSS. Features advanced theme discovery, inheritance, custom theme support, and dynamic theme switching at runtime.

Requirements

  • React 18.0+ or 19.0+
  • TypeScript 5.0+ (optional, but recommended)

The library uses only stable React APIs and is compatible with all React 18.x and 19.x versions.

Installation

npm install theme-o-rama
# or
pnpm add theme-o-rama
# or
yarn add theme-o-rama

Framework Compatibility

Theme-o-rama is designed to work seamlessly with:

  • Vite + React
  • Create React App
  • Next.js (Pages Router & App Router)
  • Remix
  • ✅ Any React SSR framework

The library is SSR-safe and includes proper guards for server-side rendering environments.

Usage

Basic Theme Definition

import { Theme } from "theme-o-rama";

const myTheme: Theme = {
  name: "my-theme",
  displayName: "My Custom Theme",
  schemaVersion: 1,
  colors: {
    background: "#ffffff",
    foreground: "#000000",
    primary: "#007bff",
    // ... other theme properties
  },
};

Tailwind CSS Integration

Theme-o-rama provides Tailwind CSS extensions that work with the dynamic theming system. To use them in your app:

@import "theme-o-rama/themes.css";
// tailwind.config.js
import { themeExtensions } from "theme-o-rama/tailwind.config.js";

export default {
  darkMode: ["class"],
  content: ["src/**/*.{ts,tsx}", "index.html"],
  theme: {
    extend: {
      // Include Theme-o-rama theme extensions
      ...themeExtensions,

      // Add your app-specific extensions here
      // keyframes: { ... },
      // animation: { ... },
    },
  },
  plugins: [require("tailwindcss-animate")],
};

The themeExtensions include:

  • Font families with CSS variables (--font-sans, --font-serif, etc.)
  • Border radius with CSS variables (--corner-sm, --corner-md, etc.)
  • Box shadows with CSS variables (--shadow-sm, --shadow-md, etc.)
  • Color system with CSS variables (--background, --foreground, etc.)

React Integration

import { ThemeProvider, useTheme } from 'theme-o-rama';

// Theme discovery function (optional)
async function discoverThemes() {
  // Your theme discovery logic here
  // Can load themes from API, file system, etc.
  return [
    { name: 'colorful', displayName: 'Colorful', schemaVersion: 1, colors: { /* ... */ } },
    { name: 'amiga', displayName: 'Amiga', schemaVersion: 1, colors: { /* ... */ } },
    // ... more themes
  ];
}

// Image resolver function (optional)
function resolveThemeImage(themeName: string, imagePath: string) {
  // Resolve theme background images
  return `/themes/${themeName}/${imagePath}`;
}

function App() {
  return (
    <ThemeProvider
      discoverThemes={discoverThemes}
      imageResolver={resolveThemeImage}
    >
      <YourApp />
    </ThemeProvider>
  );
}

function YourComponent() {
  const {
    setTheme,
    availableThemes,
    isLoading,
    error,
  } = useTheme();

  if (isLoading) return <div>Loading themes...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      <button onClick={() => setTheme('amiga')}>
        Switch to Amiga Theme
      </button>

      {/* Display available themes */}
      <div>
        {availableThemes.map(theme => (
          <button
            key={theme.name}
            onClick={() => setTheme(theme.name)}
            className={currentTheme?.name === theme.name ? 'active' : ''}
          >
            {theme.displayName}
          </button>
        ))}
      </div>
    </div>
  );
}

Next.js Integration

Theme-o-rama is fully compatible with Next.js (both Pages Router and App Router). The library is SSR-safe with proper guards for server-side rendering.

Next.js App Router (13+)

// app/layout.tsx
import { ThemeProvider } from 'theme-o-rama';
import 'theme-o-rama/themes.css';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ThemeProvider>
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

Next.js Pages Router

// pages/_app.tsx
import type { AppProps } from 'next/app';
import { ThemeProvider } from 'theme-o-rama';
import 'theme-o-rama/themes.css';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <ThemeProvider>
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

Using Themes in Next.js Components

// app/components/ThemeSelector.tsx (or pages/components/ThemeSelector.tsx)
'use client'; // Only needed for App Router

import { useTheme } from 'theme-o-rama';

export function ThemeSelector() {
  const { setTheme, currentTheme, availableThemes } = useTheme();

  return (
    <select
      value={currentTheme?.name || 'light'}
      onChange={(e) => setTheme(e.target.value)}
    >
      {availableThemes.map(theme => (
        <option key={theme.name} value={theme.name}>
          {theme.displayName}
        </option>
      ))}
    </select>
  );
}

Note: The ThemeProvider component automatically includes the 'use client' directive, making it compatible with Next.js App Router. Any component using the useTheme hook should also be a Client Component.

Preventing Flash of Unstyled Content (FOUC)

Since themes are applied client-side after hydration, you may notice a brief flash of default styling. To minimize this, add a blocking script that applies the theme before React hydrates:

Next.js App Router:

// app/layout.tsx
import { ThemeProvider } from 'theme-o-rama';
import 'theme-o-rama/themes.css';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        {/* Blocking script to prevent FOUC */}
        <script
          dangerouslySetInnerHTML={{
            __html: `
              (function() {
                try {
                  const theme = localStorage.getItem('theme') || 'light';

                  // Apply theme class immediately
                  document.documentElement.className = 'theme-' + theme;
                  document.documentElement.setAttribute('data-theme', theme);

                  // Set color-scheme for browser UI
                  document.documentElement.style.colorScheme = theme === 'dark' ? 'dark' : 'light';
                } catch (e) {
                  console.error('Theme initialization error:', e);
                }
              })();
            `,
          }}
        />
      </head>
      <body>
        <ThemeProvider>
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

Next.js Pages Router:

// pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html suppressHydrationWarning>
      <Head>
        {/* Blocking script to prevent FOUC */}
        <script
          dangerouslySetInnerHTML={{
            __html: `
              (function() {
                try {
                  const theme = localStorage.getItem('theme') || 'light';

                  // Apply theme class immediately
                  document.documentElement.className = 'theme-' + theme;
                  document.documentElement.setAttribute('data-theme', theme);

                  // Set color-scheme for browser UI
                  document.documentElement.style.colorScheme = theme === 'dark' ? 'dark' : 'light';
                } catch (e) {
                  console.error('Theme initialization error:', e);
                }
              })();
            `,
          }}
        />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

What this script does:

  • Runs before React hydration (blocking)
  • Reads theme from localStorage
  • Sets theme-{name} class on <html> element
  • Sets data-theme attribute for CSS targeting
  • Sets color-scheme for native browser UI elements
  • Provides basic theme indication before full theme loads

Add supporting CSS for instant feedback:

/* In your global CSS or theme-o-rama/themes.css */
:root {
  color-scheme: light;
}

:root[data-theme="dark"],
:root.theme-dark {
  color-scheme: dark;
}

/* Optional: Add basic color transitions for smooth theme changes */
html {
  transition:
    background-color 0.2s ease,
    color 0.2s ease;
}

The suppressHydrationWarning prop on <html> is needed because the blocking script modifies the DOM before React hydration, which would otherwise cause a warning.

Theme Discovery and Image Resolution

Theme-o-rama supports dynamic theme discovery through the discoverThemes prop. Standard light and dark themes are always included.

This allows you to load themes from source of your choice:

// Example: Loading themes from a file system (Vite)
async function discoverThemes() {
  const themeModules = import.meta.glob("../themes/*/theme.json", {
    eager: true,
  });

  return Object.entries(themeModules)
    .map(([path, module]) => {
      const match = path.match(/\.\.\/themes\/([^/]+)\/theme\.json$/);
      if (match) {
        return module as Theme;
      }
      return null;
    })
    .filter((theme): theme is Theme => theme !== null);
}

// Example: Loading themes from an API
async function discoverThemesFromAPI() {
  const response = await fetch("/api/themes");
  return response.json();
}

// Example: Loading themes from static imports
async function discoverStaticThemes() {
  return [await import("./themes/amiga.json"), await import("./themes/colorful.json")];
}

The imageResolver prop allows you to resolve theme background images:

// Example: Resolving local theme images
function resolveThemeImage(themeName: string, imagePath: string) {
  const imageModules = import.meta.glob("../themes/*/*.{jpg,jpeg,png,gif,webp}", { eager: true });
  const resolvedPath = `../themes/${themeName}/${imagePath}`;
  const imageModule = imageModules[resolvedPath];

  if (imageModule) {
    return (imageModule as { default: string }).default;
  }

  return `../themes/${themeName}/${imagePath}`;
}

// Example: Resolving remote images
function resolveRemoteThemeImage(themeName: string, imagePath: string) {
  return `https://your-cdn.com/themes/${themeName}/${imagePath}`;
}

Advanced Usage

Theme Inheritance

Themes can inherit from other themes and override specific properties:

// Inherited theme
const darkTheme: Theme = {
  name: "my-dark",
  displayName: "Dark Theme",
  schemaVersion: 1,
  inherits: "dark", // Inherits from dark theme
  colors: {
    background: "#1a1a1a",
    foreground: "#ffffff",
    // primary is inherited from dark theme
  },
  // fonts and corners are inherited from dark theme
};

Custom Theme with Component Styling

const customTheme: Theme = {
  name: "cyberpunk",
  displayName: "Cyberpunk",
  schemaVersion: 1,
  mostLike: "dark",
  backgroundImage: "cyberpunk-bg.jpg",
  backgroundSize: "cover",
  colors: {
    background: "#0a0a0a",
    foreground: "#00ff41",
    primary: "#ff0080",
    secondary: "#00ffff",
    accent: "#ffff00",
    destructive: "#ff0040",
    border: "#00ff41",
    card: "rgba(0, 255, 65, 0.1)",
    cardForeground: "#00ff41",
  },
  fonts: {
    sans: "Orbitron, monospace",
    mono: "JetBrains Mono, monospace",
  },
  corners: {
    sm: "2px",
    md: "4px",
    lg: "8px",
  },
  shadows: {
    sm: "0 0 10px #00ff41",
    md: "0 0 20px #00ff41",
    lg: "0 0 30px #00ff41",
  },
  // Custom button styling
  buttons: {
    default: {
      background: "linear-gradient(45deg, #ff0080, #00ffff)",
      color: "#000000",
      border: "2px solid #00ff41",
      borderRadius: "4px",
      boxShadow: "0 0 15px #00ff41",
      hover: {
        background: "linear-gradient(45deg, #ff40a0, #40ffff)",
        transform: "scale(1.05)",
        boxShadow: "0 0 25px #00ff41",
      },
      active: {
        transform: "scale(0.95)",
      },
    },
    outline: {
      background: "transparent",
      color: "#00ff41",
      border: "2px solid #00ff41",
      hover: {
        background: "rgba(0, 255, 65, 0.1)",
        boxShadow: "0 0 15px #00ff41",
      },
    },
  },
  // Button style flags for CSS effects
  buttonStyles: ["gradient", "shimmer"],
  // Custom table styling
  tables: {
    background: "rgba(0, 255, 65, 0.05)",
    border: "1px solid #00ff41",
    borderRadius: "8px",
    boxShadow: "0 0 20px rgba(0, 255, 65, 0.3)",
    header: {
      background: "rgba(0, 255, 65, 0.1)",
      color: "#00ff41",
      border: "1px solid #00ff41",
      fontWeight: "bold",
      backdropFilter: "blur(10px)",
    },
    row: {
      background: "transparent",
      color: "#ffffff",
      hover: {
        background: "rgba(0, 255, 65, 0.1)",
        color: "#00ff41",
      },
      selected: {
        background: "rgba(255, 0, 128, 0.2)",
        color: "#ff0080",
      },
    },
  },
  // Custom switch styling
  switches: {
    checked: {
      background: "linear-gradient(45deg, #ff0080, #00ffff)",
    },
    unchecked: {
      background: "#333333",
    },
    thumb: {
      background: "#ffffff",
    },
  },
  // Sidebar styling
  sidebar: {
    background: "rgba(0, 255, 65, 0.1)",
    border: "1px solid #00ff41",
    backdropFilter: "blur(20px)",
  },
};

Development

# Install dependencies
npm install

# Build the library
npm run build

# Watch for changes during development
npm run dev

# Clean build artifacts
npm run clean

License

Apache 2.0