Web

Dynamic Primary Color in Tailwind CSS with CSS Variables

Learn to set dynamic primary colors in Tailwind CSS using CSS variables for automatic shades like bg-primary-200. Configure tailwind.config.js with rgb(var(--primary) / <alpha-value>) for runtime theming, dark mode, and opacity support without rebuilding.

1 answer 1 view

How can I dynamically set a primary color in Tailwind CSS using CSS variables, so that shades like bg-primary-200 automatically apply variations of that color?

For example, is it possible to use:

html
<div className="bg-primary-200"></div>

And define the primary color in global CSS:

css
:root {
 --primary-color: blue;
}

So that bg-primary-200 renders the 200 shade of blue?

Yes — you can make Tailwind CSS use a runtime primary color with CSS variables so classes like bg-primary-200 automatically reflect that palette. The recommended pattern is to expose RGB or HSL channel values as CSS variables (either one variable per shade, or a base channel plus generated shades) and map those variables in tailwind.config.js using the alpha-aware syntax such as rgb(var(--primary-200) / <alpha-value>) or hsl(var(--primary) / <alpha-value>). Change the variables in :root, a theme class, or via JS and the Tailwind utilities update in the browser without rebuilding.


Contents


Customizing Tailwind Colors with CSS Variables

Short answer: :root { --primary-color: blue; } by itself doesn’t give you usable numeric channels for Tailwind’s alpha-aware functions. To let utilities like bg-primary-200 and bg-primary-200/50 work, you should expose channel values (RGB or HSL) in CSS variables and reference them in your Tailwind theme.

Why? Tailwind’s color entries in tailwind.config.js can accept CSS color functions. When you use the pattern rgb(var(--your-var) / <alpha-value>) or hsl(var(--your-var) / <alpha-value>), Tailwind keeps an <alpha-value> placeholder in the compiled CSS so utilities that change opacity (e.g., /50) or text-primary/70 still work at runtime. See the official Tailwind docs on customizing colors for the canonical examples.

Example: store RGB channel values (no commas) or HSL channel values:

css
:root{
 /* RGB channels (space-separated) */
 --primary-50: 249 250 255;
 --primary-100: 241 245 255;
 --primary-200: 224 242 254;
 --primary-500: 59 130 246; /* base */

 /* or a single HSL base */
 --primary-hsl: 212 89% 56%;
}

Then reference those variables from Tailwind so the compiled utilities use the variable at runtime.

Reference examples and patterns from community writeups: Yuurrific’s guide explains exposing channel values and mapping to shades, and TrueCoderGuru shows the rgb(var(--color) / <alpha-value>) pattern in practice: https://www.yuurrific.com/dev/mastering-tailwindcss-colors and https://truecoderguru.com/blog/tailwind/tailwind-dynamic-theming-with-css-variables.


Defining a dynamic primary color in tailwind.config.js

Add a color entry for primary that uses CSS variables and Tailwind’s <alpha-value> placeholder. Tailwind will compile class rules like bg-primary-200 to use the CSS variable, and classes like bg-primary-200/50 will inject opacity.

Example tailwind.config.js (works with Tailwind v3 and v4 patterns):

js
// tailwind.config.js
module.exports = {
 theme: {
 extend: {
 colors: {
 primary: {
 50: 'rgb(var(--primary-50) / <alpha-value>)',
 100: 'rgb(var(--primary-100) / <alpha-value>)',
 200: 'rgb(var(--primary-200) / <alpha-value>)',
 300: 'rgb(var(--primary-300) / <alpha-value>)',
 400: 'rgb(var(--primary-400) / <alpha-value>)',
 500: 'rgb(var(--primary-500) / <alpha-value>)',
 600: 'rgb(var(--primary-600) / <alpha-value>)',
 700: 'rgb(var(--primary-700) / <alpha-value>)',
 800: 'rgb(var(--primary-800) / <alpha-value>)',
 900: 'rgb(var(--primary-900) / <alpha-value>)',
 DEFAULT: 'rgb(var(--primary-500) / <alpha-value>)',
 },
 },
 },
 },
}

Notes:

  • The <alpha-value> token is replaced by Tailwind when you use opacity modifiers (e.g., /50) or utilities that inject alpha. This is the key to keeping opacity utilities functional.
  • The variable values must be numeric channel lists (e.g., 59 130 246), not a CSS name like blue or a hex string. For rgba() you can also use comma-separated variables, but the space + slash syntax is clean and supported broadly.

For Tailwind v4, the @theme directive and CSS-first patterns are emphasized in the docs; see Tailwind’s theme docs for v4-specific capabilities.


Generating color shades automatically (options)

You have three practical approaches to create the 50–900 palette that bg-primary-200 should pick:

  1. Define each shade yourself as CSS variables (recommended)
  • Pro: full control, predictable across browsers.
  • Con: you must generate the palette for each theme.
  • Example (CSS):
css
:root{
 --primary-50: 249 250 255;
 --primary-100: 241 245 255;
 --primary-200: 224 242 254;
 --primary-300: 186 230 253;
 --primary-400: 125 211 252;
 --primary-500: 59 130 246; /* brand color */
 --primary-600: 37 99 235;
 --primary-700: 29 78 216;
 --primary-800: 30 64 175;
 --primary-900: 30 58 138;
}
  1. Generate shades at build time (script or plugin)
  • Use a small build script (Node) or a Tailwind plugin (see the Merott gist plugin example) to convert a base color into channel values and emit --primary-200 etc. This keeps your source palette minimal and generates variables automatically.
  1. Generate tints at runtime with modern CSS (experimental)
  • Modern CSS supports color-mix() and other functions to derive tints from a base value; browser support is improving but not universal. If you rely on color-mix you should test targets carefully. For production stability, precomputed shade variables are safer.

Community discussions (including the Reddit thread and practical guides) confirm the per-shade-variable pattern is the most interoperable and easy to reason about: https://www.reddit.com/r/tailwindcss/comments/1i4z5ss/apply_opacity_to_theme_colors/ and https://www.yuurrific.com/dev/mastering-tailwindcss-colors.


Runtime theming and dark mode

Once you map Tailwind colors to CSS variables, switching themes is trivial and instant — no rebuild:

Method A — Theme class or data-attribute:

css
/* default (light) */
:root { --primary-500: 59 130 246; --primary-200: 224 242 254; }

/* dark or alternate theme */
[data-theme="rose"] {
 --primary-500: 219 39 119;
 --primary-200: 255 208 217;
}

/* Tailwind utilities reference the same variables, so they change automatically */

Toggle via JS:

js
// quick toggle
document.documentElement.setAttribute('data-theme', 'rose');

Method B — Update variables directly:

js
// change base color channels directly
document.documentElement.style.setProperty('--primary-500', '219 39 119');
document.documentElement.style.setProperty('--primary-200', '255 208 217');

Because the compiled CSS uses the variables, the browser repaints with the new colors immediately. Guides that demonstrate dynamic theming with Tailwind explain this flow in depth; see https://truecoderguru.com/blog/tailwind/tailwind-dynamic-theming-with-css-variables.


Practical example — React + Tailwind

  1. src/styles.css (Tailwind entry)
css
@tailwind base;
@tailwind components;
@tailwind utilities;

/* theme variables */
:root{
 --primary-50: 249 250 255;
 --primary-100: 241 245 255;
 --primary-200: 224 242 254;
 --primary-500: 59 130 246;
}
  1. tailwind.config.js (excerpt)
js
module.exports = {
 content: ['./src/**/*.{js,jsx,ts,tsx,html}'],
 theme: {
 extend: {
 colors: {
 primary: {
 50: 'rgb(var(--primary-50) / <alpha-value>)',
 100: 'rgb(var(--primary-100) / <alpha-value>)',
 200: 'rgb(var(--primary-200) / <alpha-value>)',
 500: 'rgb(var(--primary-500) / <alpha-value>)',
 DEFAULT: 'rgb(var(--primary-500) / <alpha-value>)',
 },
 },
 },
 },
}
  1. React usage
jsx
export default function Example() {
 return (
 <div className="p-6">
 <div className="bg-primary-200 text-primary-700 p-4 rounded">
 This is bg-primary-200
 </div>
 <button onClick={() => {
 document.documentElement.style.setProperty('--primary-500', '219 39 119');
 document.documentElement.style.setProperty('--primary-200', '255 208 217');
 }}>
 Switch to Rose
 </button>
 </div>
 );
}

Try bg-primary-200/50 or text-primary-700/80 — Tailwind will fill <alpha-value> so the opacity variations work as expected.


Common pitfalls and tips

  • Don’t set --primary-color: blue; if you plan to use rgb(var(--primary) / <alpha-value>). Named colors or hex strings can’t be parsed as numeric channels. Use 59 130 246 (RGB channels) or 212 89% 56% (HSL channels with hsl(var(--primary) / <alpha>)).
  • If you need rgba() instead, you can use comma-separated variables (older pattern): rgba(var(--primary-rgb), 0.5) but the space+slash rgb(var(--...) / <alpha-value>) is cleaner.
  • Ensure the classes you use (e.g., bg-primary-200) exist in your source files or safelist them so Tailwind’s content scanner doesn’t purge them.
  • Browser support: rgb(... / <alpha>) and CSS variables are widely supported in modern browsers; experimental features like color-mix() may not be safe for all targets.
  • If you want to avoid manually creating all shades, consider a small build step to generate RGB variables from a base color or use a community plugin (see the gist example): https://gist.github.com/Merott/d2a19b32db07565e94f10d13d11a8574.
  • For better maintainability, store palettes per theme (e.g., :root, .dark, [data-theme="brand"]) so switching is just a class or attribute update.

Sources


Conclusion

Yes — bg-primary-200 can follow a dynamic primary color if you expose channel values as CSS variables and map Tailwind shades to those variables in tailwind.config.js using rgb(... / <alpha-value>) or hsl(... / <alpha-value>). Prefer per-shade CSS variables (or a build step/plugin to generate them) for predictable shades, then switch themes by swapping variables in :root or a theme attribute — the UI updates instantly without a rebuild.

Authors
Verified by moderation
Moderation
Dynamic Primary Color in Tailwind CSS with CSS Variables