Skip to content

blazeshomida/shadcn-tw-plugin

Repository files navigation

shadcn-tw-plugin

shadcn-tw-plugin is a Tailwind CSS plugin designed to provide an easy and flexible way to customize themes for the shadcn component library. It allows you to define your colors using CSS color syntax such as HSL, RGB, HEX, or even the color() function. The plugin automatically converts your colors to the OKLCH color space, enabling alpha transparency and a wider color gamut. It generates the necessary CSS variables and utility classes for Tailwind CSS, providing autocomplete support when using the VSCode Tailwind IntelliSense extension.

Note: This package has no relation to shadcn. It is a personal project.

Note: This package is currently experimental. While it should be feature complete, I aim to fix any reported bugs and gather feedback to improve the package. Future releases will address known issues and any new bugs discovered.

Features

  • 🎨 Easy Theme Customization: Define and customize themes for your shadcn components effortlessly.
  • 💻 TypeScript Support: Enjoy strong type-checking and autocompletion when defining themes and custom tokens.
  • 🔧 Flexible Configuration: Customize the plugin with options like colorPrefix, defaultColorScheme, radius, and custom themes.
  • 🌈 CSS Variables and Utility Classes: Automatically generates CSS variables and Tailwind CSS utility classes for your themes.
  • 🛠️ Support for Custom Tokens: Easily add and manage custom tokens with the WithForeground utility type.
  • 🔌 Integration with Other Plugins: Seamlessly combine with other Tailwind CSS plugins for extended functionality.

Installation

Install the plugin using your preferred package manager. For example, with pnpm:

pnpm add -D shadcn-tw-plugin tailwindcss-animate

Note: For some animations to work, you need to install the tailwindcss-animate plugin.

Usage

To use the shadcn-tw-plugin in your Tailwind CSS configuration, import the plugin and add it to the plugins array. You can customize the plugin using the available options.

Basic Usage

Here is an example of a basic setup:

// tailwind.config.ts
import type { Config } from "tailwindcss";
import { shadcnTwPlugin } from "shadcn-tw-plugin";

const config = {
  darkMode: ["class"],
  content: [
    "./pages/**/*.{ts,tsx}",
    "./components/**/*.{ts,tsx}",
    "./app/**/*.{ts,tsx}",
    "./src/**/*.{ts,tsx}",
  ],
  theme: {},
  plugins: [require("tailwindcss-animate"), shadcnTwPlugin],
} satisfies Config;

export default config;

Back To Top

Advanced Usage

Inline Plugin Options

The most basic way to customize themes is to inline the plugin options directly within the Tailwind CSS configuration. This is ideal for simple customizations where you do not need to extract themes into separate objects.

Note: Light and dark are special theme keys that, when provided values, still allow you to access all the other defaults from shadcn. This means your overrides are merged with the shadcn defaults.

Example:

// tailwind.config.ts
import type { Config } from "tailwindcss";
import { shadcnTwPlugin } from "shadcn-tw-plugin";

const config = {
  // ...other config options
  plugins: [
    require("tailwindcss-animate"),
    shadcnTwPlugin({
      colorPrefix: "clr",
      defaultColorScheme: "dark",
      radius: "0.75rem",
      themes: {
        // The final theme will include the other default tokens such as `destructive`, `primary`, etc.
        light: {
          background: "hsl(0, 0%, 100%)",
          foreground: "hsl(0, 0%, 3.9%)",
        },
        dark: {
          background: "hsl(0, 0%, 3.9%)",
          foreground: "hsl(0, 0%, 98%)",
        },
      },
    }),
  ],
} satisfies Config;

export default config;

Back To Top

Extracting Themes with ThemesConfig

If you want to extract themes to their own object, you can utilize the ThemesConfig type. This approach helps keep your Tailwind CSS configuration cleaner and more maintainable, especially when dealing with multiple themes.

Example:

// tailwind.config.ts
import type { Config } from "tailwindcss";
import { shadcnTwPlugin, ThemesConfig } from "shadcn-tw-plugin";

const themes = {
  light: {
    background: "hsl(0, 0%, 100%)",
    foreground: "hsl(0, 0%, 3.9%)",
    // other tokens...
  },
  dark: {
    background: "hsl(0, 0%, 3.9%)",
    foreground: "hsl(0, 0%, 98%)",
    // other tokens...
  },
} satisfies ThemesConfig;

const config = {
  // ...other config options
  plugins: [
    require("tailwindcss-animate"),
    shadcnTwPlugin({
      themes,
    }),
  ],
} satisfies Config;

export default config;

Back To Top

Adding Custom Tokens with Generics

If you need to add custom tokens to your themes, you can pass a generic to ThemesConfig. Additionally, you can use the WithForeground type to define tokens that have both primary and foreground variants. This method provides strong type-checking and autocompletion support.

Example:

// tailwind.config.ts
import type { Config } from "tailwindcss";
import { shadcnTwPlugin, ThemesConfig, WithForeground } from "shadcn-tw-plugin";

const customThemes = {
  light: {
    background: "hsl(0, 0%, 100%)",
    foreground: "hsl(0, 0%, 3.9%)",
    tertiary: "hsl(210, 50%, 70%)",
    "tertiary-foreground": "hsl(210, 50%, 90%)",
  },
  dark: {
    background: "hsl(0, 0%, 3.9%)",
    foreground: "hsl(0, 0%, 98%)",
    tertiary: "hsl(210, 50%, 70%)",
    "tertiary-foreground": "hsl(210, 50%, 90%)",
  },
} satisfies ThemesConfig<WithForeground<"tertiary">>;

const config = {
  // ...other config options
  plugins: [
    require("tailwindcss-animate"),
    shadcnTwPlugin({
      themes: customThemes,
    }),
  ],
} satisfies Config;

export default config;

Back To Top

Using Specific Tokens Inline

If you have specific tokens that you want to inline into your themes object, you can do so. The ThemesConfig type allows you to define shared tokens and specific tokens within the themes object, providing both flexibility and type safety.

Note: Typing each token/theme is not necessary since the ThemesConfig type allows you to add any custom tokens without constraint. Passing types just helps enable autocomplete for the things you want to type.

Example:

// tailwind.config.ts
import type { Config } from "tailwindcss";
import { shadcnTwPlugin, ThemesConfig, WithForeground } from "shadcn-tw-plugin";

const themes = {
  light: {
    background: "hsl(0, 0%, 100%)",
    foreground: "hsl(0, 0%, 3.9%)",
    tertiary: "hsl(210, 50%, 70%)",
    "tertiary-foreground": "hsl(210, 50%, 90%)",
    sun: "hsl(40, 100%, 75%)",
    // other tokens...
  },
  dark: {
    background: "hsl(0, 0%, 3.9%)",
    foreground: "hsl(0, 0%, 98%)",
    tertiary: "hsl(210, 50%, 70%)",
    "tertiary-foreground": "hsl(210, 50%, 90%)",
    moon: "hsl(200, 100%, 30%)",
    // other tokens...
  },
}

 satisfies ThemesConfig<
  WithForeground<"tertiary">,
  {
    light: "sun";
    dark: "moon";
  }
>;

const config = {
  // ...other config options
  plugins: [
    require("tailwindcss-animate"),
    shadcnTwPlugin({
      themes,
    }),
  ],
} satisfies Config;

export default config;

Back To Top

Separating Everything

If you prefer to separate everything into individual objects, you can do so with the TokenMap type. This method is the most modular and allows for the greatest flexibility, though it may be more complex to manage.

Example:

// tailwind.config.ts
import type { Config } from "tailwindcss";
import {
  shadcnTwPlugin,
  ThemesConfig,
  TokenMap,
  WithForeground,
} from "shadcn-tw-plugin";

type SharedTokens = WithForeground<"tertiary">;

const lightTheme = {
  background: "hsl(0, 0%, 100%)",
  foreground: "hsl(0, 0%, 3.9%)",
  tertiary: "hsl(210, 50%, 50%)",
  "tertiary-foreground": "hsl(210, 50%, 90%)",
  sun: "hsl(40, 100%, 75%)",
} satisfies TokenMap<SharedTokens | "sun">;

const darkTheme = {
  background: "hsl(0, 0%, 3.9%)",
  foreground: "hsl(0, 0%, 98%)",
  tertiary: "hsl(210, 50%, 50%)",
  "tertiary-foreground": "hsl(210, 50%, 90%)",
  moon: "hsl(200, 100%, 30%)",
} satisfies TokenMap<SharedTokens | "moon">;

const themes = {
  light: lightTheme,
  dark: darkTheme,
} satisfies ThemesConfig;

const config = {
  // ...other config options
  plugins: [
    require("tailwindcss-animate"),
    shadcnTwPlugin({
      colorPrefix: "clr", // Custom prefix for CSS variables
      defaultColorScheme: "dark", // Use the dark theme as the default
      radius: "0.75rem", // Custom border radius
      themes, // Custom theme definitions
    }),
  ],
} satisfies Config;

export default config;

Back To Top

Combining with Other Plugins

The shadcnTwPlugin can be combined with other Tailwind CSS plugins for extended functionality. Here's an example:

Example:

// tailwind.config.ts
import type { Config } from "tailwindcss";
import { shadcnTwPlugin } from "shadcn-tw-plugin";

const config = {
  // ...other config options
  plugins: [
    require("tailwindcss-animate"),
    require("@tailwindcss/forms"),
    require("@tailwindcss/typography"),
    shadcnTwPlugin,
  ],
} satisfies Config;

export default config;

Back To Top

Options

themes
  • Type: ThemesConfig
  • Description: Custom theme definitions. This can override the default shadcn light/dark themes or add new ones.
  • Example:
    const themes = {
      light: {
        background: "hsl(0, 0%, 100%)",
        foreground: "hsl(0, 0%, 3.9%)",
        // other tokens...
      },
      dark: {
        background: "hsl(0, 0%, 3.9%)",
        foreground: "hsl(0, 0%, 98%)",
        // other tokens...
      },
    } satisfies ThemesConfig;
colorPrefix
  • Type: string

  • Default: "color"

  • Description: The prefix to use for the CSS variables.

  • Example:

    {
      colorPrefix: "clr";
    }
radius
  • Type: string
  • Default: "0.5rem"
  • Description: Border radius for card, input, and buttons.
  • Example:
    {
      radius: "0.75rem";
    }
defaultColorScheme
  • Type: "light" | "dark"
  • Default: "light"
  • Description: Determines which color scheme gets the :root selector.
  • Example:
    {
      defaultColorScheme: "dark";
    }

Back To Top

FAQ

What's the colorPrefix option for?

The colorPrefix option is used to generate CSS variables for your tokens. When you pass "clr", it generates CSS variables like this:

:root {
  --clr-background: 0, 0%, 100%;
  --clr-foreground: 0, 0%, 3.9%;
  /* and so on... */
}

Shadcn by default doesn’t use any prefix, which can be achieved by passing an empty string "". This produces CSS variables like this:

:root {
  --background: 0, 0%, 100%;
  --foreground: 0, 0%, 3.9%;
  /* and so on... */
}
What's the defaultColorScheme option for?

When generating the CSS variables, the defaultColorScheme option determines which color scheme gets the :root selector. If you decide not to use theming but still want customization, you don’t have to modify any classes or color-scheme media queries. Your customizations to that theme's variables get applied directly to the :root.

Do I need to type each token/theme?

No, typing each token/theme is not necessary. The ThemesConfig type allows you to add any custom tokens without constraint. Passing types helps enable autocomplete for the tokens you want to type, providing better developer experience and type safety.

How do the themes definitions work with default tokens?

Light and dark are special theme keys that, when provided values, still allow you to access all the other defaults from shadcn. This means your overrides are merged with the shadcn defaults. For example:

// tailwind.config.ts
import type { Config } from "tailwindcss";
import { shadcnTwPlugin } from "shadcn-tw-plugin";

const config = {
  plugins: [
    shadcnTwPlugin({
      themes: {
        light: {
          background: "hsl(0, 0%, 100%)",
          foreground: "hsl(0, 0%, 3.9%)",
        },
        dark: {
          background: "hsl(0, 0%, 3.9%)",
          foreground: "hsl(0, 0%, 98%)",
        },
      },
    }),
  ],
} satisfies Config;

export default config;

In this example, the light and dark themes will still include the default tokens like primary, secondary, destructive, etc., alongside your custom tokens.

What is the benefit of using WithForeground?

The WithForeground utility type helps define theme tokens that have both primary and foreground variants. For example, WithForeground<"card"> results in "card" and "card-foreground", ensuring consistent naming conventions and type safety across your themes.

Can I combine the shadcnTwPlugin with other Tailwind CSS plugins?

Yes, the shadcnTwPlugin can be combined with other Tailwind CSS plugins for extended functionality. Here’s an example:

// tailwind.config.ts
import type { Config } from "tailwindcss";
import { shadcnTwPlugin } from "shadcn-tw-plugin";

const config = {
  plugins: [
    require("tailwindcss-animate"),
    require("@tailwindcss/forms"),
    require("@tailwindcss/typography"),
    shadcnTwPlugin,
  ],
} satisfies Config;

export default config;

Back To Top

License

MIT