Create dynamic Tailwind CSS color palettes
Tags: tailwindcss, javascript, color, webdev
Published: 2021-08-21 14:08:07

Sure, there are tools out there that will generate for you a color palette for a given color. But will you want to manually generate it again and copy the hex values every time you update the color?

No.

Tailwind CSS gives us the ability to generate dynamic palettes, although it requires a little JavaScript function to make it work.

## Start with defining a root color

The trick is to work with HSL values.

Let's say we want to have a palette from the color `#0c8a4b` and call it `primary`. The first step is to get the HSL values. To get them, we can use this color converter website [https://convertacolor.com/](https://convertacolor.com) or any color picker like the one in the browser's DevTools.

In the HEX field, we enter `#0c8a4b`.

It will give us in return a HSL field containing `hsl(150,84%,29.4%)`.

Now in our CSS file, we define our base color:

```css
:root {
    --color-primary-h: 150;
    --color-primary-s: 84%;
    --color-primary-l: 29.4%;
}
```

Be careful, the first value `H` is not a percentage.

## Extend Tailwind CSS config

In our `tailwind.config.js` file, we'll add a small function at the top:

```js
function dynamicHsl(h, s, l) {
    return ({ opacityVariable, opacityValue }) => {
        if (opacityValue !== undefined) {
            return `hsla(${h}, ${s}, ${l}, ${opacityValue})`
        }
        if (opacityVariable !== undefined) {
            return `hsla(${h}, ${s}, ${l}, var(${opacityVariable}, 1))`
        }
        return `hsl(${h}, ${s}, ${l})`
    }
}
```

This function will convert 3 given HSL values into a CSS property. Thanks to Tailwind CSS 2.0 and above, our generated color palette will also be able to take opacity into account.

And now we extend the theme using this new function:

```js
module.exports = {
    theme: {
        extend: {
            colors: {
                primary: {
                    DEFAULT: dynamicHsl('var(--color-primary-h)', 'var(--color-primary-s)', 'var(--color-primary-l)'),
                    100: dynamicHsl('var(--color-primary-h)', 'var(--color-primary-s)', 'calc(var(--color-primary-l) + 30%)'),
                    200: dynamicHsl('var(--color-primary-h)', 'var(--color-primary-s)', 'calc(var(--color-primary-l) + 24%)'),
                    300: dynamicHsl('var(--color-primary-h)', 'var(--color-primary-s)', 'calc(var(--color-primary-l) + 18%)'),
                    400: dynamicHsl('var(--color-primary-h)', 'var(--color-primary-s)', 'calc(var(--color-primary-l) + 12%)'),
                    500: dynamicHsl('var(--color-primary-h)', 'var(--color-primary-s)', 'calc(var(--color-primary-l) + 6%)'),
                    600: dynamicHsl('var(--color-primary-h)', 'var(--color-primary-s)', 'var(--color-primary-l)'),
                    700: dynamicHsl('var(--color-primary-h)', 'var(--color-primary-s)', 'calc(var(--color-primary-l) - 6%)'),
                    800: dynamicHsl('var(--color-primary-h)', 'var(--color-primary-s)', 'calc(var(--color-primary-l) - 12%)'),
                    900: dynamicHsl('var(--color-primary-h)', 'var(--color-primary-s)', 'calc(var(--color-primary-l) - 18%)'),
                },
            },
        },
    },
};
```

By varying the `L` value alone, the palette will keep a monochromatic look, going from light to dark. Exactly what we want.

## Enjoy

This will give us these new utility classes to use where we want:

* text-primary (same as text-primary-600)
* text-primary-100 to text-primary-900

And following the same pattern:
* bg-primary
* border-primary
* ring-primary
* etc.

You can also combine these values with opacity, for instance `class="border border-primary border-opacity-25"`.

## Go further

This technique works best when the base color is not too clear and not too dark. Try other percentage values than +30% to -18% and experiment with what better fits your needs.