Skip to content

CSS Variables + TailwindCSS

TailwindCSS is a fantastic tool for quickly building attractive frontends without writing extensive custom CSS.
However, customizing it can be challenging, as most styles are embedded directly in the HTML, rather than in a separate CSS file.

So, how can we make customization easier?

The answer is CSS variables (also known as CSS custom properties).

CSS variables give and control over styles, and when combined with TailwindCSS, they are a powerful approach to theming while making maintenance easier.

Declaring CSS variable values

This document describes how to use CSS variables in the Tailwind configuration.
The actual values they represent have to be associated with the variables, which is called "declaring a CSS variable".

Declaring the CSS variables can be done in global CSS, in the admin theme configuration at
Design Configuration → <STORE> → HTML Head → Scripts and Style Sheets, or in .phtml templates.

For more details on setting the variable values, please refer to Sharing common CSS between themes.

Tailwind v3 - Hyvä 1.3.x and earlier

Tailwind v4 (Hyvä 1.4.0 and newer) is built around CSS variables, they can be used without any additional setup.
See the Tailwind v4 specific documentation on sharing CSS between themes for more info.

In the Tailwind v3 tailwind.config.js file CSS variables can be used, too, but a little care has to be taken to preserve the full capabilities of TailwindCSS. This is described below.

Adding CSS Variables to Your Tailwind Config

There are several ways to use CSS variables in a Tailwind v3 themes tailwind.config.js as described below.

Method 1: Using CSS Color Functions (rgb, hsl, etc.)

This is the traditional approach to use CSS variables with TailwindCSS v3.
It’s reliable and has broad browser support, making it the most common choice.

Declare the color variables along with Tailwind's opacity modifier in your tailwind.config.js using hsl or rgb CSS color functions.

Here’s an example:

module.exports = hyvaModules.mergeTailwindConfig({
    theme: {
        extend: {
            colors: {
                primary: {
                    lighter: 'hsl(var(--color-primary-lighter) / <alpha-value>)',
                    "DEFAULT": 'hsl(var(--color-primary) / <alpha-value>)',
                    darker: 'hsl(var(--color-primary-darker) / <alpha-value>)'
                },
            }
        }
    }
    // The rest of your Tailwind config...
});

With this setup, you can reference --color-primary and adjust it dynamically using hsl() or any other color function, making it flexible for defining colors and other properties like sizing.

This method is not limited to colors — it can be applied to sizing and spacing utilities as well.

For more details, check the official TailwindCSS documentation on CSS variables.

Opacity Modifiers

This setup uses hsl or rgb CSS color functions to retain TailwindCSS's opacity modifier functionality.
Hex color values aren't compatible with Tailwind's opacity modifiers when using CSS variables.
Use HSL or RGB syntax for opacity support.

If you don't need opacity modifiers, you can also use the hex color syntax (or hsl and rgb) but at the cost of losing Tailwind's built-in opacity functionality.

Method 2: Using the twProps and twVar Functions

As of version 1.0.10 of the @hyva-themes/hyva-modules package, you can use two helper functions, twProps and twVar, to define CSS variables in your Tailwind config.

Upgrading @hyva-themes/hyva-modules to v1.0.10 or newer

To update the library to a newer version, first change into your themes' web/tailwind directory.
To upgrade to 1.0.10 specifically, run npm upgrade @hyva-themes/hyva-modules@1.0.10
To upgrade to the latest version, run npm upgrade @hyva-themes/hyva-modules

The twProps and twVar functions rely on the CSS color-mix function. Sadly, this method doesn't work on some older browsers.
However, it is ready for production in all modern browsers, which is why it was chosen by TailwindCSS as the way forward.

Please refer to caniuse.com/wf-color-mix and the browser compatibility table on MDN for more details.

Using twProps

To get started, import twProps and/or twVar from @hyva-themes/hyva-modules in your Tailwind config:

const { twProps, mergeTailwindConfig } = require('@hyva-themes/hyva-modules');

module.exports = mergeTailwindConfig({
    ...
})

In the example above, the global import named hyvaModules is replaced with a destructuring assignment.

The twProps function can be used to set the CSS variables for our primary and secondary colors.

The following example uses twProps to declare CSS variables for all Tailwind Tokens inside of twProps:

module.exports = mergeTailwindConfig({
    theme: {
        extend: {
            colors: twProps({
                primary: {
                    lighter: colors.blue["600"],
                    DEFAULT: colors.blue["700"],
                    darker: colors.blue["800"],
                },
            })
        }
    }
    // The rest of your Tailwind config...
})

This will generate the following styles:

.bg-primary-lighter {
    --tw-bg-opacity: 1;
    background-color: color-mix(in srgb, var(--color-primary-lighter, #2563eb) calc(var(--tw-bg-opacity) * 100%), transparent);
}

.bg-primary {
    --tw-bg-opacity: 1;
    background-color: color-mix(in srgb, var(--color-primary, #1d4ed8) calc(var(--tw-bg-opacity) * 100%), transparent);
}

.bg-primary-darker {
    --tw-bg-opacity: 1;
    background-color: color-mix(in srgb, var(--color-primary-darker, #1e40af) calc(var(--tw-bg-opacity) * 100%), transparent);
}

The color value in the configuration is used as a fallback in case the CSS variable is not defined or has no valid CSS color value.
If the variable is declared with a valid value, it will override the default value from the config.

Set background, border and text colors in one go

This example only sets the color tokens and not the background, border, and text colors.

By setting the color tokens, we are also setting the background, border, and text colors, which reduces the configuration size, if the background, border, and text colors are not set with the same key.

For further assistance, refer to the [README on the GitHub page]{:target="_blank"} and the Mozilla Docs on CSS Variables.

Using twVar

The twVar function is similar to twProps but for single values.

module.exports = mergeTailwindConfig({
    theme: {
        extend: {
            colors: {
                primary: {
                    lighter: twVar('primary-lighter', colors.blue["600"]),
                    DEFAULT: twVar('primary', colors.blue["700"]),
                    darker: twVar('primary-darker', colors.blue["800"]),
                },
            }
        }
    }
    // The rest of your Tailwind config...
})

The benefit of twVar over using var directly (for example var(--color-primary-lighter, cornflowerblue)) is that it supports the Tailwind opacity modifier regardless of which color syntax is used.

Example Tailwind Config using twProps and twVar Functions

Finally, here’s a tailwind.config.js file with the new twProps added and a cleaned-up version of the file.

tailwind.config.js with CSS Variables support
const colors = require("tailwindcss/colors");
const { twProps, mergeTailwindConfig } = require("@hyva-themes/hyva-modules");

/**
 * The Hyvä Tailwind
 * For customizing this file please see the TailwindCSS Docs
 *
 * @link https://tailwindcss.com/docs/configuration
 */

/** @type {import('tailwindcss').Config} */
module.exports = mergeTailwindConfig({
    // Examples for excluding patterns from purge
    content: [
        // this theme's phtml and layout XML files
        "../../**/*.phtml",
        "../../*/layout/*.xml",
        "../../*/page_layout/override/base/*.xml",
        // parent theme in Vendor (if this is a child-theme)
        // "../../../../../../../vendor/hyva-themes/magento2-default-theme/**/*.phtml",
        // "../../../../../../../vendor/hyva-themes/magento2-default-theme/*/layout/*.xml",
        // "../../../../../../../vendor/hyva-themes/magento2-default-theme/*/page_layout/override/base/*.xml",
        // app/code phtml files (if need tailwind classes from app/code modules)
        //'../../../../../../../app/code/**/*.phtml',
    ],
    theme: {
        extend: {
            fontFamily: {
                sans: ["Segoe UI", "Helvetica Neue", "Arial", "sans-serif"],
            },
            colors: twProps({
                primary: {
                    lighter: colors.purple["600"],
                    DEFAULT: colors.purple["700"],
                    darker: colors.purple["800"],
                },
                secondary: {
                    lighter: colors.purple["100"],
                    DEFAULT: colors.purple["200"],
                    darker: colors.purple["300"],
                },
                background: {
                    lighter: colors.purple["100"],
                    DEFAULT: colors.purple["200"],
                    darker: colors.purple["300"],
                },
                container: {
                    lighter: "#f5f5f5",
                    DEFAULT: "#e7e7e7",
                    darker: "#b6b6b6",
                },
            }),
            backgroundColor: twProps({
                container: {
                    lighter: "#ffffff",
                    DEFAULT: "#fafafa",
                    darker: "#f5f5f5",
                },
            }),
            textColor: ({ theme }) => ({
                ...twProps(
                    {
                        primary: {
                            lighter: colors.gray["700"],
                            DEFAULT: colors.gray["800"],
                            darker: colors.gray["900"],
                        },
                        secondary: {
                            lighter: colors.gray["400"],
                            DEFAULT: colors.gray["600"],
                            darker: colors.gray["800"],
                        },
                    },
                    "text"
                ),
                // Extends and uses the same colors for the text, background, and border
                ...twProps({
                    color: {
                        primary: theme("colors.primary"),
                        secondary: theme("colors.secondary"),
                    },
                }),
                // Fallback for `text-red`, will be removed in the future
                ...{ red: { DEFAULT: colors.red["500"] } },
            }),
            minHeight: {
                a11y: "44px",
                "screen-25": "25vh",
                "screen-50": "50vh",
                "screen-75": "75vh",
            },
            maxHeight: {
                "screen-25": "25vh",
                "screen-50": "50vh",
                "screen-75": "75vh",
            },
            container: {
                center: true,
                padding: "1.5rem",
            },
        },
    },
    plugins: [
        require("@tailwindcss/forms"),
        require("@tailwindcss/typography"),
    ],
});

NOTE: This is an example TailwindCSS v3 configuration, use at your own risk.

Using CSS Variables in Your Code with TailwindCSS

When working with TailwindCSS, it’s a good practice to use vanilla CSS for custom properties (CSS variables).

This ensures flexibility, especially when you need to reuse the same values across multiple components or themes.
By using CSS variables, you can make your theme more dynamic and maintainable.

Replacing the theme() Function with CSS Variables

In TailwindCSS v4, it’s recommended to define styles using CSS variables directly, rather than relying on Tailwind’s theme() function. Here’s an example of how you can transition from Tailwind v3’s utility-based approach to CSS variables:

.btn {
  background-color: var(--color-primary);
  /*
     Instead of using Tailwind’s v3 method:
     background-color: theme('colors.primary.DEFAULT');
  */
}

This approach allows managing styles efficiently when working on larger projects requiring theme customization.
The Tailwind configuration doesn't need to be changed every time a color needs to be adjusted, and the design is consistent across components by sharing the same CSS variables.

Using CSS Variables with Tailwind Utilities

If your utility classes already include CSS variables, you can use them without any additional setup. For example, a button styled with the bg-primary class will automatically inherit the --color-primary variable if it's defined in your Tailwind configuration.

However, what happens when the required CSS variable isn’t included in the utility classes? TailwindCSS provides a solution: arbitrary value utilities.

Tailwind Arbitrary Values and CSS Variables

Arbitrary values allow you to directly reference CSS variables in your utility classes. Here’s an example of how to use an arbitrary value for the text-color utility with a custom CSS variable:

<div class="text-[var(--alt-color)]">
  Custom Text Color
</div>

In this case, --alt-color can be any CSS variable you’ve defined elsewhere. With arbitrary value utilities, you don’t need to wrap the value in var(), but doing so can help maintain backward compatibility with older TailwindCSS versions that don’t support arbitrary values natively.

You can learn more about arbitrary values in the TailwindCSS documentation.

Caution with Arbitrary Values

Be careful not to overuse arbitrary values. While they offer a lot of flexibility, they can also increase the size of your CSS file quickly.
It’s usually better to define your variables in the configuration and use them globally.

Setting CSS Variables with PHP

A practical use case for combining CSS variables with TailwindCSS is when you need to pass dynamic values from PHP into your styles.
For example, consider a gallery component in Hyvä UI that dynamically adjusts based on the size of the gallery thumbnails.

Since TailwindCSS doesn’t directly support dynamic values, you can use a CSS variable in combination with PHP to dynamically calculate and assign values.
Here’s an example how that could look:

<div id="gallery" ...>
  <div
    class="grid grid-rows-[auto_var(--thumbs-size)]"
    style="--thumbs-size: <?= $smallWidth ?>px;"
  >
    <!-- Gallery Content -->
  </div>
</div>

In this example:

  • The grid row size is dynamically set using the --thumbs-size CSS variable.
  • The PHP value $smallWidth is passed into the inline style, making it a dynamic and responsive setup.

This approach allows you to bridge server-side logic and CSS. It enables flexible and dynamic layouts that TailwindCSS alone can’t handle.