DEV Community

Cover image for Create dynamic Tailwind CSS color palettes
William Arin
William Arin

Posted on

Create dynamic Tailwind CSS color palettes

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?


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 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:

:root {
    --color-primary-h: 150;
    --color-primary-s: 84%;
    --color-primary-l: 29.4%;
Enter fullscreen mode Exit fullscreen mode

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:

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})`
Enter fullscreen mode Exit fullscreen mode

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:

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%)'),
Enter fullscreen mode Exit fullscreen mode

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


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.

Top comments (4)

seemstechies profile image
Sergey Inozemcev • Edited

I little bit upgrade your code:

 function pallete(color) {

    const h = `var(--color-${color}-h)`
    const s = `var(--color-${color}-s)`
    const l = `var(--color-${color}-l)`

    return {
      DEFAULT: dynamicHsl(h, s, l),
      100: dynamicHsl(h, s, `calc(${l} + 30%)`),
      200: dynamicHsl(h, s, `calc(${l} + 24%)`),
      300: dynamicHsl(h, s, `calc(${l} + 18%)`),
      400: dynamicHsl(h, s, `calc(${l} + 12%)`),
      500: dynamicHsl(h, s, `calc(${l} + 6%)`),
      600: dynamicHsl(h, s, l),
      700: dynamicHsl(h, s, `calc(${l} - 6%)`),
      800: dynamicHsl(h, s, `calc(${l} - 12%)`),
      900: dynamicHsl(h, s, `calc(${l} - 18%)`),
Enter fullscreen mode Exit fullscreen mode

Now all we need to put color name like that:

theme: {
    colors: {
"byzgu-white": pallete('byzgu-white'),
Enter fullscreen mode Exit fullscreen mode
cromozooom profile image
Razvan Nicu

Awesome and thanks for the tips.
Q: are there a way to use lightness as a modifier in tailwind?
Ex: for now we I have acces to alpha: text-primary-500/20 where 20 is alpha/opacity value. I would like to have text-primary/10/20 where 10 is lightening increase from primary and 20 is alpha/opacity.

Thank you

salk52 profile image
salk52 • Edited

Can someone explain me how these parameters are passed in returning function

return ({ opacityVariable, opacityValue }) => {

maynor96 profile image
maynor96 • Edited