Configuration

Share defaults across recipes with createTV, tweak defaultConfig globally, and customize conflict-resolution merge config.

Configure merge behavior once, then reuse it across every recipe in your design system.

Global defaultConfig

To change settings for every tv call, mutate the exported defaultConfig value:

import { defaultConfig } from 'tailwind-variants';

defaultConfig.twMerge = false;

{ twMerge: false } turns merging off at runtime. It does not remove the table from the bundle. Use /lite for that.

Prefer createTV when only some recipes should share a config. Mutating defaultConfig affects the whole process — useful for app-wide defaults, easy to overreach in libraries.

createTV

createTV returns a tv function with baked-in config. On the default entry it accepts twMerge and debug. twMergeConfig needs the table compiler, so import from tailwind-variants/config:

import { createTV } from 'tailwind-variants/config';

const tv = createTV({
  twMerge: true,
  twMergeConfig: {
    extend: {
      classGroups: {
        'font-size': ['text-tiny', 'text-small']
      }
    }
  }
});

const button = tv({
  base: 'font-medium',
  variants: {
    size: {
      tiny: 'text-tiny',
      small: 'text-small'
    }
  }
});

Every recipe created with this tv inherits the same merge settings.

twMergeConfig

Extend or override the built-in merge groups when you use custom Tailwind tokens. twMergeConfig is only supported by tailwind-variants/config. The default entry throws when a recipe is defined with it:

import { createTV } from 'tailwind-variants/config';

const tv = createTV({
  twMergeConfig: {
    extend: {
      theme: {
        spacing: ['gap-grid']
      },
      classGroups: {
        gap: [{ gap: ['grid'] }]
      }
    },
    override: {
      // replace default groups when needed
    }
  }
});

Prefer { extend, override } over replacing the entire config. Existing twMergeConfig objects from tailwind-merge generally keep working. cacheSize and prefix on that object are applied. experimentalParseClassName is not supported; see Migration.

To keep the default or lite import, compile the config once and inject the result through twMerge:

import { createTwMerge } from 'tailwind-variants/config';
import { createTV } from 'tailwind-variants';

const tv = createTV({
  twMerge: createTwMerge({ extend: { classGroups: { gap: [{ gap: ['grid'] }] } } })
});

The config entry also exports extendTailwindMerge, createTailwindMerge, fromTheme, validators, mergeConfigs, and getDefaultConfig.

Per-recipe config

Pass config as the second argument to tv for one-off overrides:

import { tv } from 'tailwind-variants';

const badge = tv(
  { base: 'rounded-full px-2', variants: { /* ... */ } },
  { twMerge: false }
);

Per-recipe config wins over createTV defaults for that instance. Per-recipe twMergeConfig also needs tv from tailwind-variants/config.

The config object also accepts the experimental debug flag for tracing how a recipe resolves. It runs in development only and costs nothing in production. See Debugging.

Lite build

The lite entry has no built-in table. Inject a merge function if you already have one:

import { createCN, createTV } from 'tailwind-variants/lite';
import { twMerge } from 'tailwind-merge';

export const tv = createTV({ twMerge });
export const cn = createCN({ twMerge });

createTV does not bind cn. A function twMerge is invoked. true or omitting twMerge still joins only. twMergeConfig and debug are not available on lite.

To use a custom table on lite, compile it on the config entry and inject it:

import { createTwMerge } from 'tailwind-variants/config';
import { createTV } from 'tailwind-variants/lite';

export const tv = createTV({ twMerge: createTwMerge(twMergeConfig) });

On this page