DEV Community

Ankit Jain
Ankit Jain

Posted on

A modifier-first approach to writing tailwindCSS

Tailwind Modifier

Why tailwind-modifier?

Tailwind CSS is a powerful utility-first framework, but managing complex modifiers like hover, focus, group-hover, and responsive breakpoints can become repetitive and error-prone, especially in large-scale projects. Developers often face challenges with:

  • Modifier Overhead: Writing and maintaining repeated modifier combinations leads to cluttered and less readable code.
  • Nested Modifiers: Expressing deeply nested or conditional states requires verbose and cumbersome syntax.
  • Readability & Maintainability: Teams struggle to review and understand heavily modified Tailwind classes, especially when multiple layers of logic are involved.
  • Type Safety: Lack of native type-checking makes it harder to catch errors when creating dynamic or custom modifier configurations.

These challenges can slow down development, reduce code clarity, and increase the likelihood of bugs in Tailwind-based projects.
tailwind-modifier aims to solve these problems by introducing a modifier-first approach that simplifies the process, reduces repetition, and enhances readability while providing full type safety.

tailwind-modifier

Tailwind-modifier adopts a modifier-first approach to writing Tailwind classes, simplifying the management and handling of screens, groups, and
other modifiers available in Tailwind CSS.

Features

✓ Complete type-safety

✓ No external dependencies

✓ Full support for all Tailwind CSS modifiers

✓ Nested modifier support - *:dark:bg-dark

✓ Differentiated peers/groups support - group-hover/item

✓ Full compatibility with arbitrary modifiers - group-[.is-published]

✓ Support for arbitrary variant modifiers - [&:nth-child(3)]:underline

✓ Custom modifiers support for type-safety

✓ Works seamlessly with other tools like twMerge and clsx

✓ Support for tailwind classes auto-completion. See Editor Support below

Installation

You can install tailwind-modifier using npm or your choice of package manager:

npm install tailwind-modifier
Enter fullscreen mode Exit fullscreen mode

Usage

twMod offers versatile syntax options, providing users with the flexibility to seamlessly integrate the tool into their workflow.

  1. Pass simple comma separated string with modifiers.
twMod('text-blue-300 hover:font-bold,translateY-[0.5px],text-red-300');

// text-blue-300 hover:font-bold hover:translateY-[0.5px] hover:text-red-300
Enter fullscreen mode Exit fullscreen mode
  1. Pass multiple string arguments. twMod which merge the classes (will not do any conflict resolution if duplicates found).
twMod(
  'p-4 rounded text-blue-300',
  'hover:font-bold,translateY-[0.5px],text-red-300',
  'group-hover:uppercase,border',
);

// p-4 rounded text-blue-300 hover:font-bold hover:translateY-[0.5px] hover:text-red-300 group-hover:uppercase group-hover:border
Enter fullscreen mode Exit fullscreen mode

Note: you can pass the output of this to twMerge for conflict resolution.

  1. True power of twMod comes with the object syntax which is type-safe and allows for nesting of modifiers.
twMod({
  DEFAULT: 'w-3/4 px-16 py-8',
  sm: 'w-full px-4 py-4',
  md: 'w-5/6 px-8 py-8',
});

// Or
twMod(
  'w-3/4 px-16 py-8',
  {
    // This is still type-safe
    sm: 'w-full px-4 py-4',
    md: 'w-5/6 px-8 py-8',
  },
);

// w-3/4 px-16 py-8 sm:w-full sm:px-4 sm:py-4 md:w-5/6 md:px-8 md:py-8
Enter fullscreen mode Exit fullscreen mode

Note: use the DEFAULT reserved key to set default/root classes

twMod({
  DEFAULT: 'text-gray-800 bg-white p-4',
  hover: {
    DEFAULT: 'bg-gray-100',
    dark: {
      DEFAULT: 'bg-gray-900 text-white',
      focus: 'ring ring-gray-300 outline-none',
    },
  },
  sm: {
    DEFAULT: 'p-2 text-gray-700',
    hover: {
      DEFAULT: 'bg-gray-200',
      dark: {
        DEFAULT: 'text-gray-300',
        active: 'bg-gray-800',
      },
    },
  },
  lg: {
    DEFAULT: 'p-8 text-gray-900',
    'group-hover': {
      DEFAULT: 'bg-blue-200',
      'peer-hover': {
        DEFAULT: 'bg-blue-500',
        focus: 'ring ring-blue-300',
      },
    },
  },
});

// or
const defaultScreen: TwModInput = {
  DEFAULT: 'text-gray-800 bg-white p-4',
  hover: {
    DEFAULT: 'bg-gray-100',
    dark: {
      DEFAULT: 'bg-gray-900 text-white',
      focus: 'ring ring-gray-300 outline-none',
    },
  },
};
const smallScreen: TwModInput = {
  sm: {
    DEFAULT: 'p-2 text-gray-700',
    hover: {
      DEFAULT: 'bg-gray-200',
      dark: {
        DEFAULT: 'text-gray-300',
        active: 'bg-gray-800',
      },
    },
  },
};
const largeScreen: TwModInput = {
  lg: {
    DEFAULT: 'p-8 text-gray-900',
    'group-hover': {
      DEFAULT: 'bg-blue-200',
      'peer-hover': {
        DEFAULT: 'bg-blue-500',
        focus: 'ring ring-blue-300',
      },
    },
  },
};

twMod(defaultScreen, smallScreen, largeScreen);

// text-gray-800 bg-white p-4 hover:bg-gray-100 hover:dark:bg-gray-900 hover:dark:text-white hover:dark:focus:ring
// hover:dark:focus:ring-gray-300 hover:dark:focus:outline-none sm:p-2 sm:text-gray-700 sm:hover:bg-gray-200
// sm:hover:dark:text-gray-300 sm:hover:dark:active:bg-gray-800 lg:p-8 lg:text-gray-900 lg:group-hover:bg-blue-200
// lg:group-hover:peer-hover:lg:group-hover:peer-hover:focus:ring lg:group-hover:peer-hover:focus:ring-blue-300
Enter fullscreen mode Exit fullscreen mode
  1. You can use this with clsx tool for conditional classes or can directly set conditional classes in twMod
// Conditional classes with clsx
twMod({
  DEFAULT: 'w-10 p-4',
  sm: ['bg-green-200', clsx({ 'w-20 bg-green-300': open })],
  md: clsx({ 'text-white bg-gray-800': isDarkMode }),
  lg: {
    DEFAULT: 'rounded-lg shadow-lg',
    hover: clsx({ 'bg-blue-500 scale-110': isActive }),
    'group-focus': clsx({ 'ring-2 ring-green-400': open }),
  },
  xl: [
    'p-8 text-lg',
    {
      dark: clsx({ 'text-gray-200 bg-gray-900': isDarkMode }),
      'group-active': clsx({ 'bg-yellow-300': open }),
    },
  ],
});

// Or without clsx
twMod({
  DEFAULT: 'w-10 p-4',
  sm: ['bg-green-200', open ? 'w-20 bg-green-300' : ''],
  md: isDarkMode && 'text-white bg-gray-800',
  lg: {
    DEFAULT: 'rounded-lg shadow-lg',
    hover: isActive && 'bg-blue-500 scale-110',
    placeholder: isActive && 'text-dark',
    'group-focus': open && 'ring-2 ring-green-400',
  },
  xl: [
    'p-8 text-lg',
    {
      dark: isDarkMode && 'text-gray-200 bg-gray-900',
      'group-active': open && 'bg-yellow-300',
    },
  ],
});

// w-10 p-4 sm:bg-green-200 md:text-white md:bg-gray-800 lg:rounded-lg lg:shadow-lg
// xl:p-8 xl:text-lg xl:dark:text-gray-200 xl:dark:bg-gray-900
Enter fullscreen mode Exit fullscreen mode
  1. Use with twMerge to additionally resolve conflicts
const overrideClassnames: TwModInput = 'sm:bg-yellow-100,text-light,text-center';
const overrideClassnames1: TwModInput = {
  sm: ['bg-yellow-200', 'text-blue-200', 'text-left'],
};
const result = twMerge(twMod(
  {
    sm: 'bg-red-100,text-white,font-bold',
  },
  overrideClassnames,
  overrideClassnames1,
);
// sm:font-bold sm:bg-yellow-200 sm:text-blue-200 sm:text-left

Enter fullscreen mode Exit fullscreen mode
  1. Use a custom instance of twMod to include the custom modifiers you've added to your Tailwind configuration.

Add a new breakpoint in tailwind configuration

/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    extend: {
      screens: {
        '3xl': '1600px',
      },
    },
  },
  plugins: [],
};
Enter fullscreen mode Exit fullscreen mode

Create new instance of TwMod with default configuration

const twModCustom = createTwModWithDefaults({
  customModifiers: ['3xl'],
});
twModCustom({
  DEFAULT: 'w-10',
  '3xl': 'w-50',
});

// w-10 3xl:w-50
Enter fullscreen mode Exit fullscreen mode

Configuration

  • customModifiers: Define custom modifiers to incorporate additional values when extending your Tailwind configuration, such as introducing a new breakpoint.

Support

  • Compatible with all versions of Node.js.
  • Works in all browsers that support ECMAScript 5 (ES5).

Editor Support

For enhanced auto-completion of tailwind utility classes in the twMod function, add the following configuration to your setup
(assuming the tailwind-language-server is installed):

VS Code
Add following settings to your `settings.json` file.
Enter fullscreen mode Exit fullscreen mode
{
  "tailwindCSS.experimental.classRegex": [
    ["twMod[a-zA-Z_]*\\(([^)]*)\\)", "(?:'|\"|`|,)*([^'\"`,]*)(?:'|\"|`|,)"]
  ]
}
Enter fullscreen mode Exit fullscreen mode
Neovim
Enter fullscreen mode Exit fullscreen mode

nvim-lspconfig

["tailwindcss"] = function()
  lspconfig["tailwindcss"].setup({
    settings = {
      tailwindCSS = {
        experimental = {
          classRegex = {
            { "twMod[a-zA-Z_]*\\(([^)]*)\\)", "(?:'|\"|`|,)*([^'\"`,]*)(?:'|\"|`|,)" },
          },
        },
      },
    },
  },
end
Enter fullscreen mode Exit fullscreen mode

tailwind-tools

server = {
  settings = {
    experimental = {
      classRegex = {
        { "twMod[a-zA-Z_]*\\(([^)]*)\\)", "(?:'|\"|`|,)*([^'\"`,]*)(?:'|\"|`|,)" },
      },
    },
  },
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)