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
Declaring CSS variable values
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.
There are several ways to use CSS variables in a theme's tailwind.config.js
as described below.
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.
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 New twProps
and twVar
Functions
Available since @hyva-themes/hyva-modules v1.0.10
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
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 uses.
Powered by the CSS color-mix
function, 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 best way forward.
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.
Declaring CSS variable values
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.
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 technical documentation on the GitHub page 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
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:
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.