I built tailwindcss-expose-colors after needing the same Tailwind color in CSS and JavaScript without copying its hex value into both places.
It started with a useful Gist. I turned the snippet into a package once I needed control over which colors and shades were exposed, along with the variable prefix.
tailwindcss-expose-colors exposes selected colors from your Tailwind CSS theme as CSS variables, making them available to plain CSS and JavaScript.
The plugin is built with TypeScript and hooks into Tailwind's plugin API. Here's a snapshot of the main function:
export const exposeColors = ({
colors = ["red"],
shades = ["500"],
prefix = "--tw",
}) =>
function ({ addBase, theme }: PluginAPI) {
// ...
};It then filters and maps your theme's colors based on your configuration (tailwind.config.ts) and injects them as CSS variables into your project.
With tailwindcss-expose-colors, you can specify which colors and shades to expose and even set your preferred variable prefix. Here's an example of setting it up in tailwind.config.ts:
export default {
theme: { extend: {} },
content: ["./src/**/*.{ts,tsx}"],
plugins: [exposeColors({ colors: ["red", "green", "blue"] })],
};export default {
theme: { extend: {} },
content: ["./src/**/*.{ts,tsx}"],
plugins: [
exposeColors({
colors: ["primary", "success", "error", "base"],
shades: ["DEFAULT", "500", "700", "900"],
prefix: "--design-system",
}),
],
};Once everything is set up, you can use these colors in your CSS, like this:
body {
background: rgba(var(--tw-red-500), 0.1);
border: 1px solid rgb(var(--tw-primary)); /* --tw-primary maps to the `DEFAULT` shade */
}The original problem was setting one color from JavaScript and letting every child use it in CSS. This is a simplified version of that component:
{
// ...
blockquote: ({ children, ...props }: any) => (
<blockquote
{...props}
className={cn({
"blockquote-mask blockquote-safari relative !my-8 p-4": true,
"[--accent:var(--tw-primary)]": !alert,
"[--accent:var(--tw-blue-500)]": alert === "NOTE",
"[--accent:var(--tw-green-500)]": alert === "TIP",
"[--accent:var(--tw-purple-500)]": alert === "IMPORTANT",
"[--accent:var(--tw-amber-500)]": alert === "WARNING",
"[--accent:var(--tw-red-500)]": alert === "CAUTION"
})}>
{icon} {children}
</blockquote>
),
},The selected color becomes --accent, which the rest of the styling can use without knowing which alert type was rendered:
.blockquote-mask {
border: none;
background-size: 100%;
background-repeat: no-repeat;
background: radial-gradient(
circle 16px at top left,
#0000 100%,
rgba(var(--accent), 0.1)
)
top left;
}
.blockquote-mask::before {
content: "";
width: 4px;
height: calc(100% - 15px); /* The mask -1px for a small overlap */
position: absolute;
top: 15px;
left: 0;
background: linear-gradient(
to bottom,
rgba(var(--accent), 0.8) 0%,
rgba(var(--accent), 0.8) 50%,
rgba(var(--accent), 0.1) 100%
);
}
.blockquote-mask svg {
fill: rgba(var(--accent), 0.8);
}Now the alert colors stay tied to the theme even when the theme changes, unlike my first approach of copying each hex value.
The plugin emits comma-separated RGB channels rather than a complete color, so each use can choose its own alpha value with rgba(var(--tw-red-500), 0.5). It also exposes only the colors and shades you select, avoiding a variable for every color in the theme.
It is a small package for one recurring problem: keeping Tailwind as the source of truth when a color needs to cross into CSS or JavaScript. The source, setup, and issue tracker are all in the tailwindcss-expose-colors repository.
latest commit : 41 additions 16 deletions