V1.0

cn

Utilities for combining classNames with or without custom tailwind configurations to manage style conflicts.

Installation

Install the following dependencies:

terminal
npm install clsx tailwind-merge

Create a utils/cn.ts file and paste the following code into it.

/utils/cn.ts
import { borderRadii, shadows, texts } from '@/tailwind.config';
import clsx, { type ClassValue } from 'clsx';
import { extendTailwindMerge } from 'tailwind-merge';
 
export { type ClassValue } from 'clsx';
 
export const twMergeConfig = {
  extend: {
    classGroups: {
      'font-size': [
        {
          text: Object.keys(texts),
        },
      ],
      shadow: [
        {
          shadow: Object.keys(shadows),
        },
      ],
      rounded: [
        {
          rounded: Object.keys(borderRadii),
        },
      ],
    },
  },
};
 
const customTwMerge = extendTailwindMerge(twMergeConfig);
 
/**
 * Utilizes `clsx` with `tailwind-merge`, use in cases of possible class conflicts.
 */
export function cnExt(...classes: ClassValue[]) {
  return customTwMerge(clsx(...classes));
}
 
/**
 * A direct export of `clsx` without `tailwind-merge`.
 */
export const cn = clsx;

IntelliSense setup (optional)

If you are using VSCode and the TailwindCSS IntelliSense Extension, you have to add the following to your .vscode/settings.json file to enable Intellisense features for cn, cnExt and tv functions.

.vscode/settings.json
"tailwindCSS.experimental.classRegex": [
  ["([\"'`][^\"'`]*.*?[\"'`])", "[\"'`]([^\"'`]*).*?[\"'`]"]
]

Prettier setup (optional)

If you're using prettier-plugin-tailwindcss, you can include cn, cnExt in the functions list to ensure it gets sorted as well.

prettier.config.mjs
const config = {
  // ...
  plugins: ['prettier-plugin-tailwindcss'],
  tailwindFunctions: ['cn', 'cnExt'],
};
 
export default config;

Examples

cnExt

In this example, cnExt is used to merge Tailwind classes and conditionally apply styles based on the isActive prop while managing potential class conflicts.

/examples/cn-ext.ts
import { cnExt } from '@/utils/cn';
 
function MyComponent({
  className,
  isActive,
  ...rest
}: React.HTMLAttributes<HTMLDivElement> & {
  isActive?: boolean;
}) {
  return (
    <div
      className={cnExt(
        'text-text-strong-950 bg-bg-white-0 size-3',
        {
          'text-text-white-0 bg-bg-strong-950': isActive,
        },
        className,
      )}
      {...rest}
    />
  );
}

cn

The cnExt function provides a more comprehensive approach compared to cn, handling class conflicts with an extensive configuration which may introduce performance overhead. 1 It is advisable to use the cn function when conflict resolution is not required for better performance.

Here is an example of using the cn function to achieve the same output as the usage above.

/examples/cn.ts
import { cn } from '@/utils/cn';
 
function MyComponent({
  className,
  isActive,
  ...rest
}: React.HTMLAttributes<HTMLDivElement> & {
  isActive?: boolean;
}) {
  return (
    <div
      className={cn(
        'size-3',
        {
          'text-text-white-0 bg-bg-strong-950': isActive,
          'text-text-strong-950 bg-bg-white-0': !isActive,
        },
        className,
      )}
      {...rest}
    />
  );
}

Footnotes

  1. https://github.com/dcastil/tailwind-merge/issues/253

© 2024 AlignUI Design System. All rights reserved.