Tailwind CSS 68 🎨 Extending the Tailwind Engine with Dynamic Functional Utilities
The static utilities cover the fixed values. The dynamic utilities cover the values the developer chooses at the class-name level. The matchUtilities and the matchComponents functions are the mechanisms: they take a name prefix and a function, and the function produces the CSS from the value the class name supplies. A plugin that registers text-shadow generates text-shadow-sm, text-shadow-lg, and text-shadow-[0_4px_8px_red] from the one function. The matchVariant does the same for the variants. Together they are the mechanism that turns a plugin from a fixed set of classes into a small language.
Key point: The matchUtilities function takes the values from the theme by default. The values option supplies the allowed values, and the type option restricts the value format. When the developer uses the arbitrary value syntax, the value bypasses the theme and the function receives the raw string.
Why dynamic functional utilities exist
The fixed-value problem. A plugin that registers text-shadow-sm and text-shadow-lg covers two values. The design system adds a third. The developer edits the plugin. The matchUtilities registers the function, and the theme supplies the values, so the new value is the theme entry, not the plugin change.
The arbitrary-value problem. The design system has a one-off value. The developer wants text-shadow-[0_4px_8px_red]. The matchUtilities handles the arbitrary value out of the box: the function receives the 0 4px 8px red string, and the CSS is the result.
The repetition problem. The plugin that registers ten static classes for the ten values has ten entries. The matchUtilities registers one function, and the theme supplies the ten values. The plugin’s code does not grow with the number of values.
The type problem. The value must be a valid CSS value. The type option restricts the format: the color, the length, the number, and the percentage. The wrong value is rejected, and the generated CSS is valid.
The variant problem. The matchVariant generates the variants from the value. The @container variant and the nth-child variant are the examples. The value is the input, and the selector is the output.
The modification problem. The modifiers option handles the second value in the class name. The text-shadow-lg/red form uses the lg as the value and the red as the modifier. The function receives both.
The composition problem. The matchUtilities and the static addUtilities compose. The static utilities are the fixed defaults, and the dynamic utilities are the values. The two registrations coexist, and the classes are the union.
a. The matchUtilities
The matchUtilities takes a map of the name prefixes to the functions, and an options object.
The basic matchUtilities
const plugin = require('tailwindcss/plugin');
module.exports = plugin(({ matchUtilities, theme }) => {
matchUtilities(
{
'text-shadow': (value) => ({
textShadow: value,
}),
},
{ values: theme('textShadow') }
);
}, {
theme: {
textShadow: {
sm: '0 1px 2px rgb(0 0 0 / 0.1)',
DEFAULT: '0 2px 4px rgb(0 0 0 / 0.1)',
lg: '0 8px 16px rgb(0 0 0 / 0.1)',
},
},
});
The text-shadow prefix generates the text-shadow-sm class from the sm value, and the text-shadow-[0_4px_8px_red] from the arbitrary value.
The values option
The values option supplies the allowed values. The theme values are the common input.
matchUtilities(
{
'text-shadow': (value) => ({ textShadow: value }),
},
{ values: theme('textShadow') }
);
The DEFAULT key produces the bare text-shadow class.
The type option
The type option restricts the value format. The values are the CSS data types.
matchUtilities(
{
'text-shadow': (value) => ({ textShadow: value }),
},
{
values: theme('textShadow'),
type: 'color',
}
);
The type: 'color' accepts only the color values. The type: 'length' accepts the length values. The type: ['color', 'length'] accepts both.
The type values
| Type | Accepts |
|---|---|
color | The color values |
length | The length values |
number | The numeric values |
percentage | The percentage values |
position | The position values |
line-width | The line width |
image | The image values |
url | The URL values |
absolute-size | The font sizes |
relative-size | The relative sizes |
The modifier
The modifiers option handles the slash-separated second value.
matchUtilities(
{
'text-shadow': (value, { modifier }) => ({
textShadow: `${value} ${modifier ?? ''}`,
}),
},
{
values: theme('textShadow'),
modifiers: 'any',
}
);
The text-shadow-lg/red form passes the lg as the value and the red as the modifier.
The modifier types
| Modifier | Accepts |
|---|---|
any | Any value |
| The theme key | The theme values |
| The map | The explicit values |
The supportsVariants option
The supportsVariants option (default true) allows the variants on the generated utilities.
matchUtilities(
{ 'text-shadow': (value) => ({ textShadow: value }) },
{ values: theme('textShadow'), supportsVariants: true }
);
The hover:text-shadow-lg and the focus:text-shadow-lg are the examples.
The respectPrefix and the respectImportant
The two options control the prefix and the important.
matchUtilities(
{ 'text-shadow': (value) => ({ textShadow: value }) },
{ values: theme('textShadow'), respectPrefix: true, respectImportant: true }
);
The respectPrefix: true adds the configured prefix, and the respectImportant: true adds the !important when the important modifier is used.
The matchUtilities table
| Option | Purpose |
|---|---|
values | The allowed values |
type | The value format |
modifiers | The second value |
supportsVariants | The variant support |
respectPrefix | The prefix |
respectImportant | The important |
b. The matchComponents
The matchComponents is the component counterpart of the matchUtilities. The difference is the intent: the components are the reusable pieces, and the utilities are the atomic properties.
The basic matchComponents
module.exports = plugin(({ matchComponents, theme }) => {
matchComponents(
{
'card': (value) => ({
padding: value,
borderRadius: theme('borderRadius.lg'),
backgroundColor: theme('colors.white'),
boxShadow: theme('boxShadow.md'),
}),
},
{ values: theme('spacing') }
);
});
The card-4 class produces the card with the 1rem padding, and the card-8 with the 2rem.
The component versus the utility
| Aspect | The utility | The component |
|---|---|---|
| Intent | The atomic property | The reusable piece |
| The CSS | The one declaration | The multiple declarations |
| The examples | text-shadow-lg | card-4, btn-lg |
The distinction is the convention, not the API. The matchComponents and the matchUtilities have the same options.
The component with the multiple values
matchComponents(
{
'btn': (value, { modifier }) => ({
padding: `${value} ${parseFloat(value) * 2}px`,
borderRadius: theme('borderRadius.md'),
fontWeight: modifier ?? '500',
}),
},
{ values: theme('spacing'), modifiers: theme('fontWeight') }
);
The btn-4/bold form passes the 4 as the value and the bold as the modifier.
c. The matchVariant
The matchVariant generates the variants from the value.
The basic matchVariant
module.exports = plugin(({ matchVariant }) => {
matchVariant(
'nth',
(value) => `&:nth-child(${value})`,
{
values: {
1: '1',
2: '2',
3: '3',
odd: 'odd',
even: 'even',
},
}
);
});
The nth-2:bg-blue-500 form applies the :nth-child(2) selector.
The arbitrary value
matchVariant(
'nth',
(value) => `&:nth-child(${value})`
);
The nth-[4n+1]:bg-blue-500 form uses the arbitrary value.
The sort option
The sort option controls the variant order.
matchVariant(
'nth',
(value) => `&:nth-child(${value})`,
{ values: { ... }, sort: (a, b) => parseInt(a) - parseInt(b) }
);
The sort function orders the variants when the CSS is generated.
The container query example
matchVariant(
'@',
(value) => `@container (min-width: ${value})`,
{
values: {
sm: '24rem',
md: '28rem',
lg: '32rem',
},
sort: (a, b) => parseInt(a) - parseInt(b),
}
);
The @md:flex form applies the @container (min-width: 28rem) rule.
The matchVariant table
| Option | Purpose |
|---|---|
values | The allowed values |
sort | The variant order |
modifiers | The second value |
Complete Example Session
// ============================================
// PART 1: THE BASIC MATCH-UTILITIES
// ============================================
const plugin = require('tailwindcss/plugin');
module.exports = plugin(({ matchUtilities, theme }) => {
matchUtilities(
{
'text-shadow': (value) => ({
textShadow: value,
}),
},
{ values: theme('textShadow') }
);
}, {
theme: {
textShadow: {
sm: '0 1px 2px rgb(0 0 0 / 0.1)',
DEFAULT: '0 2px 4px rgb(0 0 0 / 0.1)',
lg: '0 8px 16px rgb(0 0 0 / 0.1)',
},
},
});
// ============================================
// PART 2: THE ARBITRARY VALUE
// ============================================
// <div class="text-shadow-[0_4px_8px_rgb(255_0_0_/_0.5)]">
// The arbitrary value bypasses the theme.
// ============================================
// PART 3: THE TYPE OPTION
// ============================================
matchUtilities(
{ 'text-shadow': (value) => ({ textShadow: value }) },
{ values: theme('textShadow'), type: 'color' }
);
// The type restricts the value to the color format.
// ============================================
// PART 4: THE MODIFIER
// ============================================
matchUtilities(
{
'text-shadow': (value, { modifier }) => ({
textShadow: modifier ? `${value} ${modifier}` : value,
}),
},
{ values: theme('textShadow'), modifiers: 'any' }
);
// <div class="text-shadow-lg/red">
// The lg is the value, the red is the modifier.
// ============================================
// PART 5: THE MATCH-COMPONENTS
// ============================================
module.exports = plugin(({ matchComponents, theme }) => {
matchComponents(
{
'card': (value) => ({
padding: value,
borderRadius: theme('borderRadius.lg'),
backgroundColor: theme('colors.white'),
}),
},
{ values: theme('spacing') }
);
});
// <div class="card-4">...</div>
// <div class="card-8">...</div>
// ============================================
// PART 6: THE MATCH-VARIANT
// ============================================
module.exports = plugin(({ matchVariant }) => {
matchVariant(
'nth',
(value) => `&:nth-child(${value})`,
{
values: { 1: '1', 2: '2', 3: '3', odd: 'odd', even: 'even' },
}
);
});
// <div class="nth-2:bg-blue-500">...</div>
// ============================================
// PART 7: THE CONTAINER QUERY VARIANT
// ============================================
matchVariant(
'@',
(value) => `@container (min-width: ${value})`,
{
values: { sm: '24rem', md: '28rem', lg: '32rem' },
sort: (a, b) => parseInt(a) - parseInt(b),
}
);
// <div class="@md:flex">...</div>
// ============================================
// PART 8: THE SUPPORTS-VARIANTS
// ============================================
matchUtilities(
{ 'text-shadow': (value) => ({ textShadow: value }) },
{ values: theme('textShadow'), supportsVariants: true }
);
// <div class="hover:text-shadow-lg">...</div>
// ============================================
// PART 9: THE RESPECT-IMPORTANT
// ============================================
matchUtilities(
{ 'text-shadow': (value) => ({ textShadow: value }) },
{ values: theme('textShadow'), respectImportant: true }
);
// <div class="text-shadow-lg!">...</div>
// ============================================
// PART 10: THE COMPLETE PATTERN
// ============================================
// 1. Use the matchUtilities for the dynamic utilities.
// 2. Supply the values with the theme.
// 3. Restrict the value with the type option.
// 4. Use the modifiers for the second value.
// 5. Use the matchComponents for the dynamic components.
// 6. Use the matchVariant for the dynamic variants.
// 7. Use the sort option for the variant order.
// 8. The arbitrary value bypasses the theme.
// 9. Use the supportsVariants for the hover and focus.
// 10. Use the respectImportant for the important modifier.
The ten parts showed the basic matchUtilities, the arbitrary value, the type option, the modifier, the matchComponents, the matchVariant, the container query variant, the supportsVariants, the respectImportant, and the complete pattern.
Quick Reference
The matchUtilities Options
| Option | Purpose |
|---|---|
values | The allowed values |
type | The value format |
modifiers | The second value |
supportsVariants | The variant support |
respectPrefix | The prefix |
respectImportant | The important |
The Type Values
| Type | Accepts |
|---|---|
color | The color values |
length | The length values |
number | The numeric values |
percentage | The percentage values |
position | The position values |
image | The image values |
url | The URL values |
absolute-size | The font sizes |
The matchVariant Options
| Option | Purpose |
|---|---|
values | The allowed values |
sort | The variant order |
modifiers | The second value |
The modifier Values
| Value | Accepts |
|---|---|
any | Any value |
| The theme key | The theme values |
| The map | The explicit values |
The Common Patterns
| Pattern | Use Case |
|---|---|
| Text shadow | The dynamic shadow values |
| Animation delay | The dynamic delay values |
| Fluid type | The clamp expression |
| Container query | The dynamic variants |
| nth-child | The dynamic selectors |
Best Practices
✅ Do This:
// Use the matchUtilities for the dynamic utilities.
matchUtilities({ 'text-shadow': (v) => ({ textShadow: v }) }, { values: theme('textShadow') }); // ✅
// Supply the values with the theme.
theme: { textShadow: { sm: '...', lg: '...' } } // ✅
// Restrict the value with the type option.
{ values: theme('textShadow'), type: 'color' } // ✅
// Use the modifiers for the second value.
{ values: theme('textShadow'), modifiers: 'any' } // ✅
// Use the matchComponents for the reusable pieces.
matchComponents({ 'card': (v) => ({ padding: v }) }, { values: theme('spacing') }); // ✅
// Use the matchVariant for the dynamic variants.
matchVariant('nth', (v) => `&:nth-child(${v})`, { values: { ... } }); // ✅
// Use the sort for the variant order.
{ sort: (a, b) => parseInt(a) - parseInt(b) } // ✅
❌ Don’t Do This:
// Don't use the addUtilities for the dynamic values.
addUtilities({ '.text-shadow-sm': { ... }, '.text-shadow-lg': { ... } }); // ⚠️ Use the matchUtilities
// Don't forget the values option.
matchUtilities({ 'text-shadow': (v) => ({ textShadow: v }) }); // ⚠️ No values
// Don't use the wrong type.
{ values: theme('textShadow'), type: 'number' } // ❌ The shadow is not a number
// Don't skip the sort when the order matters.
matchVariant('nth', (v) => `&:nth-child(${v})`, { values: { ... } }); // ⚠️ The arbitrary order
// Don't use the matchComponents for the atomic properties.
matchComponents({ 'p': (v) => ({ padding: v }) }); // ❌ The p is a utility
// Don't forget the supportsVariants when the hover is wanted.
{ supportsVariants: false } // ❌ The hover:text-shadow fails
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| The class does not generate | The values option is missing | Add the values |
| The arbitrary value fails | The type restriction | Check the type |
| The modifier is ignored | The modifiers option | Add the option |
| The variant does not apply | The supportsVariants is false | Set to true |
| The order is wrong | The sort function | Add the sort |
| The prefix is missing | The respectPrefix | Set to true |
| The important is missing | The respectImportant | Set to true |
Real-World Examples
1. The Text Shadow
matchUtilities({ 'text-shadow': (v) => ({ textShadow: v }) }, { values: theme('textShadow') }),
2. The Arbitrary Value
<div class="text-shadow-[0_4px_8px_red]">
3. The Type
{ values: theme('textShadow'), type: 'color' }
4. The Modifier
{ values: theme('textShadow'), modifiers: 'any' }
5. The Card Component
matchComponents({ 'card': (v) => ({ padding: v }) }, { values: theme('spacing') }),
6. The nth-child Variant
matchVariant('nth', (v) => `&:nth-child(${v})`, { values: { ... } }),
7. The Container Query
matchVariant('@', (v) => `@container (min-width: ${v})`, { values: { ... } }),
8. The Supports Variants
{ values: theme('textShadow'), supportsVariants: true }
9. The Sort
{ sort: (a, b) => parseInt(a) - parseInt(b) }
10. The Complete Pattern
matchUtilities({ 'text-shadow': (v) => ({ textShadow: v }) }, { values: theme('textShadow') });
Visual
The matchUtilities Flow
┌────────────────────────────────────────────────────────────┐
│ THE matchUtilities FLOW │
│ │
│ matchUtilities( │
│ { 'text-shadow': (value) => ({ textShadow: value }) }, │
│ { values: theme('textShadow') } │
│ ); │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ The theme values: │ │
│ │ sm: '0 1px 2px ...' │ │
│ │ lg: '0 8px 16px ...' │ │
│ │ │ │
│ │ The generated classes: │ │
│ │ .text-shadow-sm { text-shadow: 0 1px 2px ... } │ │
│ │ .text-shadow-lg { text-shadow: 0 8px 16px ... }│ │
│ │ │ │
│ │ The arbitrary value: │ │
│ │ .text-shadow-[0_4px_8px_red] { ... } │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Type Restriction
┌────────────────────────────────────────────────────────────┐
│ THE TYPE RESTRICTION │
│ │
│ type: 'color' │
│ ┌──────────────────────────────────────────────────┐ │
│ │ text-shadow-[red] → the CSS is generated │ │
│ │ text-shadow-[4px] → rejected │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ type: 'length' │
│ ┌──────────────────────────────────────────────────┐ │
│ │ text-shadow-[4px] → the CSS is generated │ │
│ │ text-shadow-[red] → rejected │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ type: ['color', 'length'] │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Both the color and the length are accepted. │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Modifier
┌────────────────────────────────────────────────────────────┐
│ THE MODIFIER │
│ │
│ matchUtilities( │
│ { │
│ 'text-shadow': (value, { modifier }) => ({ │
│ textShadow: modifier ? `${value} ${modifier}` : value,│
│ }), │
│ }, │
│ { values: theme('textShadow'), modifiers: 'any' } │
│ ); │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ text-shadow-lg → value = lg │ │
│ │ text-shadow-lg/red → value = lg, mod = red │ │
│ │ │ │
│ │ The function composes the two values. │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The matchVariant
┌────────────────────────────────────────────────────────────┐
│ THE matchVariant │
│ │
│ matchVariant( │
│ 'nth', │
│ (value) => `&:nth-child(${value})`, │
│ { values: { 1: '1', 2: '2', odd: 'odd' } } │
│ ); │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ nth-2:bg-blue-500 │ │
│ │ → &:nth-child(2) │ │
│ │ │ │
│ │ nth-odd:bg-blue-500 │ │
│ │ → &:nth-child(odd) │ │
│ │ │ │
│ │ nth-[4n+1]:bg-blue-500 │ │
│ │ → &:nth-child(4n+1) │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
matchUtilities | The dynamic utilities |
matchComponents | The dynamic components |
matchVariant | The dynamic variants |
values | The allowed values |
type | The value format |
modifiers | The second value |
supportsVariants | The variant support |
respectPrefix | The prefix |
respectImportant | The important |
sort | The variant order |
Key takeaways:
- The
matchUtilitiesgenerates the dynamic utilities from the value. The value is the class-name suffix, and the function produces the CSS. Thevaluesoption supplies the allowed values, and the theme is the common input. - The arbitrary value bypasses the theme. The
text-shadow-[0_4px_8px_red]form passes the raw string to the function. Thetypeoption restricts the format, and the wrong value is rejected. - The
typeoption restricts the value format. Thecolor, thelength, thenumber, thepercentage, and the others are the types. Thetype: ['color', 'length']accepts both. - The
modifiersoption handles the second value. Thetext-shadow-lg/redform passes thelgas the value and theredas the modifier. Themodifiers: 'any'accepts any value. - The
matchComponentsis the component counterpart. The API is the same, and the difference is the intent: the components are the reusable pieces, and the utilities are the atomic properties. - The
matchVariantgenerates the variants from the value. Thenth-2:and the@md:are the examples. Thesortoption controls the order. - The
supportsVariantsallows the variants on the generated utilities. Thehover:text-shadow-lgand thefocus:text-shadow-lgare the examples. - The
respectPrefixand therespectImportanthandle the configured prefix and the important modifier. Thetext-shadow-lg!form adds the!importantwhen therespectImportantis true.
Remember: The dynamic functional utilities extend the Tailwind engine with the value-driven classes. The matchUtilities and the matchComponents generate the CSS from the value, and the matchVariant generates the variants. The values option supplies the theme, the type option restricts the format, and the modifiers option handles the second value. The result is a plugin that registers one function and generates the entire family of the classes, and a framework that adapts to the design system’s values.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!