| |

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

TypeAccepts
colorThe color values
lengthThe length values
numberThe numeric values
percentageThe percentage values
positionThe position values
line-widthThe line width
imageThe image values
urlThe URL values
absolute-sizeThe font sizes
relative-sizeThe 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

ModifierAccepts
anyAny value
The theme keyThe theme values
The mapThe 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

OptionPurpose
valuesThe allowed values
typeThe value format
modifiersThe second value
supportsVariantsThe variant support
respectPrefixThe prefix
respectImportantThe 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

AspectThe utilityThe component
IntentThe atomic propertyThe reusable piece
The CSSThe one declarationThe multiple declarations
The examplestext-shadow-lgcard-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

OptionPurpose
valuesThe allowed values
sortThe variant order
modifiersThe 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

OptionPurpose
valuesThe allowed values
typeThe value format
modifiersThe second value
supportsVariantsThe variant support
respectPrefixThe prefix
respectImportantThe important

The Type Values

TypeAccepts
colorThe color values
lengthThe length values
numberThe numeric values
percentageThe percentage values
positionThe position values
imageThe image values
urlThe URL values
absolute-sizeThe font sizes

The matchVariant Options

OptionPurpose
valuesThe allowed values
sortThe variant order
modifiersThe second value

The modifier Values

ValueAccepts
anyAny value
The theme keyThe theme values
The mapThe explicit values

The Common Patterns

PatternUse Case
Text shadowThe dynamic shadow values
Animation delayThe dynamic delay values
Fluid typeThe clamp expression
Container queryThe dynamic variants
nth-childThe 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

PitfallWhy It HappensFix
The class does not generateThe values option is missingAdd the values
The arbitrary value failsThe type restrictionCheck the type
The modifier is ignoredThe modifiers optionAdd the option
The variant does not applyThe supportsVariants is falseSet to true
The order is wrongThe sort functionAdd the sort
The prefix is missingThe respectPrefixSet to true
The important is missingThe respectImportantSet 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

ItemValue
matchUtilitiesThe dynamic utilities
matchComponentsThe dynamic components
matchVariantThe dynamic variants
valuesThe allowed values
typeThe value format
modifiersThe second value
supportsVariantsThe variant support
respectPrefixThe prefix
respectImportantThe important
sortThe variant order

Key takeaways:

  • The matchUtilities generates the dynamic utilities from the value. The value is the class-name suffix, and the function produces the CSS. The values option 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. The type option restricts the format, and the wrong value is rejected.
  • The type option restricts the value format. The color, the length, the number, the percentage, and the others are the types. The type: ['color', 'length'] accepts both.
  • The modifiers option handles the second value. The text-shadow-lg/red form passes the lg as the value and the red as the modifier. The modifiers: 'any' accepts any value.
  • The matchComponents is 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 matchVariant generates the variants from the value. The nth-2: and the @md: are the examples. The sort option controls the order.
  • The supportsVariants allows the variants on the generated utilities. The hover:text-shadow-lg and the focus:text-shadow-lg are the examples.
  • The respectPrefix and the respectImportant handle the configured prefix and the important modifier. The text-shadow-lg! form adds the !important when the respectImportant is 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!