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 lies in CSS variables (also known as CSS custom properties).

CSS variables give you more flexibility and control over your styles, and when combined with TailwindCSS, they allow for powerful theming options and easier maintenance.

Adding CSS Variables to Your Tailwind Config

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

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

To see a detailed setup, check the guide on Sharing Common CSS Between Themes.

In short, you can use CSS color functions (like hsl or rgb) to define color variables along with Tailwind’s opacity modifier (which itself uses a CSS variable).

Here’s how you can do that:

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, using the same approach.

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

Method 2: Using the New twProps and twVar Functions

Available since @hyva-themes/hyva-modules v1.0.10

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

These functions align with the approach TailwindCSS v4 will adopt.

Powered by the CSS color-mix function, this method is newer and has more limited browser support but is production-ready for most modern environments.

To get started, import twProps in your Tailwind config:

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

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

Here, we replaced the global import named hyvaModules with a destructured import, and added twProps to set the new CSS variables for our primary and secondary colors.

Now using twProps, we can set the CSS variables for any 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...
})

and this will generate the following styles:

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

Note

In this sample, you might notice we are only setting 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.

From here, you can customize the CSS variables the same way as explained in the Sharing Common CSS Between Themes guide, without needing to worry about which color syntax you were using.

If you still have more questions, also check out the technical docs on the GitHub page readme and the Mozilla Docs on CSS Variables.

As a final gift, here’s a tailwind.config.js file with the new twProps added and a cleaned-up version of the file. We hope to add this version in some form in a future release of Hyvä, but for now, you can find it here.

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 still a sample configuration of TailwindCSS and may change, so 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 defining CSS variables, you can make your design 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’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');
  */
}

With this setup, you can manage your styles more efficiently, especially when working on larger projects that require theme-specific customizations. You no longer need to modify the Tailwind configuration every time you change a color, and you can keep your design consistent across different components by using the same CSS variable.

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.

Arbitrary Value Utilities

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 if overused. If you have the option, it’s often better to define your variables in the configuration and use them globally.

Practical Example: PHP Integration with CSS Variables

A more practical use case for combining CSS variables with TailwindCSS is when you need to pass dynamic values, such as those generated by 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 how you can implement this:

<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 with your CSS, enabling more flexible and dynamic layouts that TailwindCSS alone can’t fully handle.