Skip to content

Instantly share code, notes, and snippets.

@Merott
Last active July 7, 2025 22:40
Show Gist options
  • Select an option

  • Save Merott/d2a19b32db07565e94f10d13d11a8574 to your computer and use it in GitHub Desktop.

Select an option

Save Merott/d2a19b32db07565e94f10d13d11a8574 to your computer and use it in GitHub Desktop.
Expose Tailwind colors as CSS custom properties (variables)

This is a simple Tailwind plugin to expose all of Tailwind's colors, including any custom ones, as custom css properties on the :root element.

There are a couple of main reasons this is helpful:

  • You can reference all of Tailwind's colors—including any custom ones you define—from handwritten CSS code.
  • You can define all of your colors within the Tailwind configuration, and access the final values programmatically, which isn't possible if you did it the other way around: referencing custom CSS variables (defined in CSS code) from your Tailwind config.

See the Tailwind Plugins for more info on plugins.

module.exports = {
  theme: {
    extend: {
      colors: {
        gray: {
          '100': '#f5f5f5',
          '200': '#eeeeee',
          '300': '#e0e0e0',
          '400': '#bdbdbd',
          '500': '#9e9e9e',
          '600': '#757575',
          '700': '#616161',
          '800': '#424242',
          '900': '#212121',
        },
      },
    },
  },
  plugins: [
    function({ addBase, theme }) {
      function extractColorVars(colorObj, colorGroup = '') {
        return Object.keys(colorObj).reduce((vars, colorKey) => {
          const value = colorObj[colorKey];

          const newVars =
            typeof value === 'string'
              ? { [`--color${colorGroup}-${colorKey}`]: value }
              : extractColorVars(value, `-${colorKey}`);

          return { ...vars, ...newVars };
        }, {});
      }

      addBase({
        ':root': extractColorVars(theme('colors')),
      });
    },
  ],
};
@sudo-vaibhav

Copy link
Copy Markdown

just to add some clarity to this otherwise amazing snippet, the colors are exposed as var(--color-color_name-intensity)

for example : var(--color-secondary-900)

@dharmapurebuddha

Copy link
Copy Markdown

Oh sweet thank you for sharing this, it was very useful for incorporating colors into a few obscure styles Tailwind doesn't implement. Cheers 🌻

@Maybach91

Copy link
Copy Markdown

I wanted to use var(--color-green) instead of var(--color-green-DEFAULT) so i changed the script a bit:

function ({ addBase, theme }) {
      function extractColorVars (colorObj, colorGroup = '') {
        return Object.keys(colorObj).reduce((vars, colorKey) => {
          const value = colorObj[colorKey];
          const cssVariable = colorKey === "DEFAULT" ? `--color${colorGroup}` : `--color${colorGroup}-${colorKey}`;

          const newVars =
            typeof value === 'string'
              ? { [cssVariable]: value }
              : extractColorVars(value, `-${colorKey}`);

          return { ...vars, ...newVars };
        }, {});
      }

      addBase({
        ':root': extractColorVars(theme('colors')),
      });
    }

@stepanjakl

Copy link
Copy Markdown

Nice one, thanks for this!

@EdvinTr

EdvinTr commented Nov 5, 2022

Copy link
Copy Markdown

This is awesome, thanks!

@superniftydev

Copy link
Copy Markdown

Exactly what I was looking for. Cheers!

@emigdio821

Copy link
Copy Markdown

Is there a way to expose only the custom colors?
These ones:

extend: {
  colors: {
    customblue: '#0274b6',
    custombluehover: '#015483',
  },
},

Thanks!

@benwinding

Copy link
Copy Markdown

Thank you for this!
Side note: an AI-driven search engine led me to this solution here! 😅
Phind -> Source link

@jbasoo

jbasoo commented May 18, 2023

Copy link
Copy Markdown

I further modified @Maybach91's modification and abstracted a bit for any theme config, not just colors (theoretically, havent tested every type 😬).

Also I switched from addBase to addUtilites as my project isn't using the base styles, only utilities.

  plugins: [
    function({ addUtilities, theme }) {    
      function extractVars (obj, group = '', prefix) {
        return Object.keys(obj).reduce((vars, key) => {
          const value = obj[key];
          const cssVariable = key === "DEFAULT" ? `--${prefix}${group}` : `--${prefix}${group}-${key}`;
          
          const newVars =
          typeof value === 'string'
          ? { [cssVariable]: value }
          : extractVars(value, `-${key}`, prefix);
          
          return { ...vars, ...newVars };
        }, {});
      }
      
      addUtilities({
        ':root': {
          ...extractVars(theme('colors'), '', 'color'),
          ...extractVars(theme('boxShadow'), '', 'box-shadow')
        }
      })
    }
  ],

@y-nk

y-nk commented Jul 10, 2023

Copy link
Copy Markdown

anybody did an official plugin for that? i'm thinking should i paste this snippet or npm install

@Maybach91

Maybach91 commented Jul 12, 2023

Copy link
Copy Markdown

anybody did an official plugin for that? i'm thinking should i paste this snippet or npm install

@y-nk Yup:
https://npms.io/search?q=tailwindcss-colors-css-variables

This looks like the most maintained one

@ianhernandez

Copy link
Copy Markdown

Anyone have a typescript version of this?

@y-nk

y-nk commented Aug 19, 2023 via email

Copy link
Copy Markdown

@mathieutu

mathieutu commented Sep 5, 2023

Copy link
Copy Markdown

I works like a charm in Tailwind v3.3.3, thanks! 🎉

Here is a TS simplified version:

import { type PluginAPI } from 'tailwindcss/types/config'

export const colorVarsPlugin = ({ addBase, theme }: PluginAPI) => {
  const extractColorVars = (colorObj: Record<string, string>, colorGroup = '') => (
    Object.entries(colorObj)
      .reduce((vars, [colorKey, value]) => {
        const cssVariable = colorKey === 'DEFAULT' ? `--color${colorGroup}` : `--color${colorGroup}-${colorKey}`

        const newVars: Record<string, string> = typeof value === 'string'
          ? { [cssVariable]: value }
          : extractColorVars(value, `-${colorKey}`)

        return { ...vars, ...newVars }
      }, {})
  )

  addBase({
    ':root': extractColorVars(theme('colors')),
  })
}

EDIT: Indeed, it'd be great to find a way to purge the colors..

@joshdavenport

joshdavenport commented Oct 18, 2023

Copy link
Copy Markdown

A note to people stumbling on this gist as I did: This plugin is awesome, really nice idea, but consider adding variables to your CSS manually. Using something like the below, as long as you have access in your CSS to tailwind directives, you can reference colours and create variables from them:

:root {
  --tw-color-white: theme('colors.white');
  --tw-color-black: theme('colors.black');
  --tw-color-gray-100: theme('colors.gray.100');
  --tw-color-gray-200: theme('colors.gray.200');
  --tw-color-gray-300: theme('colors.gray.300');
  --tw-color-gray-400: theme('colors.gray.400');
  --tw-color-gray-500: theme('colors.gray.500');
  --tw-color-gray-600: theme('colors.gray.600');
  --tw-color-gray-700: theme('colors.gray.700');
  --tw-color-gray-800: theme('colors.gray.800');
  --tw-color-gray-900: theme('colors.gray.900');
  --tw-color-gray-950: theme('colors.gray.950');
}

For example, if you're using Next and following the globals.css pattern the docs describe you can add this to globals.css.

Doing it that way you don't need to worry about the bundle size reports others have mentioned (haven't looked at it myself, so no idea how badly it affects the size). Obviously the whole point of the plugin is quickly and easily provide access to all colours, but maybe you don't need that, so consider what you need contextually.

@oskarengstrom

oskarengstrom commented Nov 29, 2023

Copy link
Copy Markdown

A note to people stumbling on this gist as I did: This plugin is awesome, really nice idea, but consider adding variables to your CSS manually. Using something like the below, as long as you have access in your CSS to tailwind directives, you can reference colours and create variables from them:

@joshdavenport
This sounds great, but exactly how do you use theme() in globals.css?

@simonhamp

Copy link
Copy Markdown

@oskarengstrom if you're generating your CSS through a bundler, like Vite or Webpack, then the theme() function will be exposed to you.

It won't work in plain CSS

@rafaelrcamargo

Copy link
Copy Markdown

That's great! If anyone wants, here is a version that exports "R, G, B" values so you can use RGBA and play with the alpha in CSS:

import type { PluginAPI } from "tailwindcss/types/config"

export const colorsToVars = ({ addBase, theme }: PluginAPI) => {
  const extractColorVars = (
    colorObj: Record<string, string>,
    colorGroup = ""
  ) =>
    Object.entries(colorObj).reduce((vars, [colorKey, value]) => {
      const cssVariable =
        colorKey === "DEFAULT"
          ? `--tw${colorGroup}`
          : `--tw${colorGroup}-${colorKey}`

      const newVars: Record<string, string> =
        typeof value === "string"
          ? { [cssVariable]: parseColor(value)?.color.join(", ") }
          : extractColorVars(value, `-${colorKey}`)

      return { ...vars, ...newVars }
    }, {})

  addBase({ ":root": extractColorVars(theme("colors")) })
}

/**
 * Color Parser
 * Grabbed this from Tailwind's source code :)
 */

let HEX = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i
let SHORT_HEX = /^#([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i
let VALUE = /(?:\d+|\d*\.\d+)%?/
let SEP = /(?:\s*,\s*|\s+)/
let ALPHA_SEP = /\s*[,/]\s*/
let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)(?:,(?:[^ )]*?|var\(--[^ )]*?\)))?\)/

let RGB = new RegExp(
  `^(rgba?)\\(\\s*(${VALUE.source}|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`
)
let HSL = new RegExp(
  `^(hsla?)\\(\\s*((?:${VALUE.source})(?:deg|rad|grad|turn)?|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`
)

export function parseColor(value: string, { loose = false } = {}) {
  value = value.trim()

  let hex = value
    .replace(SHORT_HEX, (_, r, g, b, a) =>
      ["#", r, r, g, g, b, b, a ? a + a : ""].join("")
    )
    .match(HEX)

  if (hex !== null) {
    return {
      mode: "rgb",
      color: [
        parseInt(hex[1]!, 16),
        parseInt(hex[2]!, 16),
        parseInt(hex[3]!, 16)
      ].map(v => v.toString()),
      alpha: hex[4] ? (parseInt(hex[4], 16) / 255).toString() : undefined
    }
  }

  let match = value.match(RGB) ?? value.match(HSL)
  if (match === null) return null

  let color = [match[2], match[3], match[4]]
    .filter(Boolean)
    .map(v => v!.toString())

  if (color.length === 2 && color[0]!.startsWith("var("))
    return { mode: match[1], color: [color[0]], alpha: color[1] }

  if (!loose && color.length !== 3) return null

  if (color.length < 3 && !color.some(part => /^var\(.*?\)$/.test(part)))
    return null

  return { mode: match[1], color, alpha: match[5]?.toString?.() }
}

The variables will be in "R, G, B" format so you can use them as follows:

  fill: rgba(var(--tw-green-500), 0.8);

@GeorgeCht

Copy link
Copy Markdown

If anyone is still interested for a plugin on this check @tailwind-plugin/expose-colors.

@Merott

Merott commented Jan 9, 2024

Copy link
Copy Markdown
Author

I wish we had reaction emojis in gists!

Just wanted to say I ❤️ all the ways in which this little snippet (that I'm no longer even using myself) has been adapted! 😁

@sudo-vaibhav

Copy link
Copy Markdown

@Merott +1, glad to have been among the first people who contributed to this gist, and see what twisted new creative ways people have extended it. I come back to it every 6 months to see what has become of this gist.

@Travis-Enright

Travis-Enright commented Mar 18, 2024

Copy link
Copy Markdown

@Merott Thanks! This helped a lot. I used a bit of a variation on it because I'm using a deeply-nested colors object and didn't prefer the color prefix. This did the trick for me:

function ({ addBase, theme }) {
      function extractColorVars (colorObj, colorGroup = '') {
        return Object.entries(colorObj).reduce((vars, [key, value]) => {
          const varKey = key === 'DEFAULT' ? `${colorGroup}` : `${colorGroup}-${key}`
          if (typeof value === 'string') {
            return { ...vars, [`-${varKey}`]: value }
          } else {
            return { ...vars, ...extractColorVars(value, varKey) }
          }
        }, {});
      }

      addBase({
        ':root': extractColorVars(theme('colors')),
      });
    }

@daxdesai

Copy link
Copy Markdown

Assuming you have already added TailwindCSS to your project and that your CSS file is called global.css.

First, you need to edit global.css to look like this:

@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --primary-color: #fff;
  --secondary-color: #000;
}

And then, in order to be able to use them, you need to update tailwind.config.js with the new CSS variables like so:

module.exports = {
  theme: {
    extend: {
      colors: {
        "primary-color": "var(--primary-color)",
        "secondary-color": "var(--secondary-color)"
      },
    },
  },
};

You can now use these variables as desired:

<div class="bg-primary-color">
  <h1>Hello World</h1>
</div>

@wpinfusion

Copy link
Copy Markdown

For whoever wants to only expose their custom colors, put them all in a separate array:

extend: {
  colors: {
    brand: {
      yellow: '#FFF4C6',
    }
  }
}

And use theme('colors.brand') in the addBase() function.

@t-mart

t-mart commented Aug 4, 2024

Copy link
Copy Markdown
:root {
  --tw-color-white: theme('colors.white');
  --tw-color-black: theme('colors.black');
  --tw-color-gray-100: theme('colors.gray.100');
  --tw-color-gray-200: theme('colors.gray.200');
  --tw-color-gray-300: theme('colors.gray.300');
  --tw-color-gray-400: theme('colors.gray.400');
  --tw-color-gray-500: theme('colors.gray.500');
  --tw-color-gray-600: theme('colors.gray.600');
  --tw-color-gray-700: theme('colors.gray.700');
  --tw-color-gray-800: theme('colors.gray.800');
  --tw-color-gray-900: theme('colors.gray.900');
  --tw-color-gray-950: theme('colors.gray.950');
}

This is the right play. Thanks @joshdavenport.

  • No plugin needed
  • It's opt-in to colors to "variablize" in the bundle
  • Intuitive as to what the values will replace to
  • Keeps the config as the source of truth.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment