Skip to content

Instantly share code, notes, and snippets.

@Merott
Last active April 26, 2024 11:06
Show Gist options
  • Star 119 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save Merott/d2a19b32db07565e94f10d13d11a8574 to your computer and use it in GitHub Desktop.
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')),
      });
    },
  ],
};
@simonhamp
Copy link

@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

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

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

@Merott
Copy link
Author

Merott commented Jan 9, 2024

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

@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
Copy link

Travis-Enright commented Mar 18, 2024

@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

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>

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