First-class variant API for Tailwind CSS that enables developers to create reusable component styles with type-safe variants, slots support, and automatic conflict resolution
—
The core variant API provides the main functionality for creating styled components with variants, slots, compound variants, and default values. This is the primary interface for building reusable component styles.
The primary function for creating variant-enabled styled components.
const tv: TV;The tv function accepts a configuration object and returns a styled component function that can be called with variant props to generate class names.
Creates a custom tv instance with specific configuration.
function createTV(config: TVConfig): TV;Parameters:
config - Configuration object for tailwind-merge integrationReturns: A tv function instance with the specified configuration
The tv function accepts a configuration object with the following options:
interface TVOptions<V, S, B, EV, ES> {
extend?: TVReturnType;
base?: B;
slots?: S;
variants?: V;
compoundVariants?: TVCompoundVariants<V, S, B, EV, ES>[];
compoundSlots?: TVCompoundSlots<V, S, B>[];
defaultVariants?: TVDefaultVariants<V, S, EV, ES>;
}Base classes applied to all variants of the component.
const button = tv({
base: "font-medium rounded-lg transition-colors focus:outline-none focus:ring-2"
});Named variant groups with their corresponding classes.
const button = tv({
base: "font-medium rounded-lg",
variants: {
color: {
primary: "bg-blue-500 text-white hover:bg-blue-600",
secondary: "bg-gray-500 text-white hover:bg-gray-600",
danger: "bg-red-500 text-white hover:bg-red-600",
},
size: {
sm: "px-3 py-1.5 text-sm",
md: "px-4 py-2 text-base",
lg: "px-6 py-3 text-lg",
},
disabled: {
true: "opacity-50 cursor-not-allowed",
false: "cursor-pointer",
},
},
});Named component parts that can each have their own styling variants.
const card = tv({
slots: {
base: "rounded-lg border shadow-sm",
header: "px-6 py-4 border-b",
body: "px-6 py-4",
footer: "px-6 py-4 border-t bg-gray-50",
},
variants: {
size: {
sm: {
base: "max-w-sm",
header: "px-4 py-2 text-sm",
body: "px-4 py-2 text-sm",
footer: "px-4 py-2 text-sm",
},
lg: {
base: "max-w-4xl",
header: "px-8 py-6 text-xl",
body: "px-8 py-6",
footer: "px-8 py-6",
},
},
},
});
// Usage with slots
const { base, header, body, footer } = card({ size: "lg" });Conditional styles applied when specific variant combinations are active.
const button = tv({
base: "font-medium rounded-lg",
variants: {
color: {
primary: "bg-blue-500 text-white",
secondary: "bg-gray-500 text-white",
},
size: {
sm: "px-3 py-1.5 text-sm",
lg: "px-6 py-3 text-lg",
},
},
compoundVariants: [
{
color: "primary",
size: "lg",
class: "shadow-lg shadow-blue-500/25",
},
{
color: ["primary", "secondary"],
size: "sm",
class: "font-semibold",
},
],
});Conditional slot styles applied when specific variant combinations are active.
const card = tv({
slots: {
base: "rounded-lg border",
header: "px-6 py-4 border-b",
},
variants: {
color: {
primary: { base: "border-blue-200" },
danger: { base: "border-red-200" },
},
},
compoundSlots: [
{
color: "primary",
slots: ["header"],
class: "bg-blue-50 text-blue-900",
},
],
});Default variant values applied when no specific variants are provided.
const button = tv({
variants: {
color: {
primary: "bg-blue-500",
secondary: "bg-gray-500",
},
size: {
sm: "text-sm px-3 py-1.5",
md: "text-base px-4 py-2",
},
},
defaultVariants: {
color: "primary",
size: "md",
},
});
// Uses default variants (primary, md)
const defaultButton = button();Compose and extend existing tv components.
const baseButton = tv({
base: "font-medium rounded-lg",
variants: {
size: {
sm: "px-3 py-1.5 text-sm",
md: "px-4 py-2 text-base",
},
},
});
const iconButton = tv({
extend: baseButton,
base: "inline-flex items-center gap-2",
variants: {
iconPosition: {
left: "flex-row",
right: "flex-row-reverse",
},
},
});const alert = tv({
base: "p-4 rounded-md border",
variants: {
variant: {
info: "bg-blue-50 border-blue-200 text-blue-800",
success: "bg-green-50 border-green-200 text-green-800",
warning: "bg-yellow-50 border-yellow-200 text-yellow-800",
error: "bg-red-50 border-red-200 text-red-800",
},
},
defaultVariants: {
variant: "info",
},
});
// Usage
const infoAlert = alert(); // Uses default 'info' variant
const errorAlert = alert({ variant: "error" });const modal = tv({
slots: {
overlay: "fixed inset-0 bg-black/50 flex items-center justify-center",
content: "bg-white rounded-lg shadow-xl max-w-md w-full mx-4",
header: "px-6 py-4 border-b border-gray-200",
body: "px-6 py-4",
footer: "px-6 py-4 border-t border-gray-200 flex justify-end gap-3",
},
variants: {
size: {
sm: {
content: "max-w-sm",
header: "px-4 py-3 text-sm",
body: "px-4 py-3 text-sm",
footer: "px-4 py-3",
},
lg: {
content: "max-w-2xl",
header: "px-8 py-6 text-xl",
body: "px-8 py-6",
footer: "px-8 py-6",
},
},
},
});
// Usage
const { overlay, content, header, body, footer } = modal({ size: "lg" });type TVReturnType<V extends TVVariants<S>, S extends TVSlots, B extends ClassValue, EV extends TVVariants<ES>, ES extends TVSlots, E extends TVReturnType = undefined> = {
(props?: TVProps<V, S, EV, ES>): HasSlots<S, ES> extends true
? {
[K in keyof (ES extends undefined ? {} : ES)]: (slotProps?: TVProps<V, S, EV, ES>) => string;
} & {
[K in keyof (S extends undefined ? {} : S)]: (slotProps?: TVProps<V, S, EV, ES>) => string;
} & {
[K in TVSlotsWithBase<{}, B>]: (slotProps?: TVProps<V, S, EV, ES>) => string;
}
: string;
} & TVReturnProps<V, S, B, EV, ES, E>;
type TVProps<V extends TVVariants<S>, S extends TVSlots, EV extends TVVariants<ES>, ES extends TVSlots> =
EV extends undefined
? V extends undefined
? ClassProp<ClassValue>
: {
[K in keyof V]?: StringToBoolean<keyof V[K]> | undefined;
} & ClassProp<ClassValue>
: V extends undefined
? {
[K in keyof EV]?: StringToBoolean<keyof EV[K]> | undefined;
} & ClassProp<ClassValue>
: {
[K in keyof V | keyof EV]?:
| (K extends keyof V ? StringToBoolean<keyof V[K]> : never)
| (K extends keyof EV ? StringToBoolean<keyof EV[K]> : never)
| undefined;
} & ClassProp<ClassValue>;
type TVCompoundVariants<V extends TVVariants<S>, S extends TVSlots, B extends ClassValue, EV extends TVVariants<ES>, ES extends TVSlots> = Array<
{
[K in keyof V | keyof EV]?:
| (K extends keyof V ? StringToBoolean<keyof V[K]> : never)
| (K extends keyof EV ? StringToBoolean<keyof EV[K]> : never)
| (K extends keyof V ? StringToBoolean<keyof V[K]>[] : never);
} & ClassProp<SlotsClassValue<S, B> | ClassValue>
>;
type TVCompoundSlots<V extends TVVariants<S>, S extends TVSlots, B extends ClassValue> = Array<
V extends undefined
? {
slots: Array<TVSlotsWithBase<S, B>>;
} & ClassProp
: {
slots: Array<TVSlotsWithBase<S, B>>;
} & {
[K in keyof V]?: StringToBoolean<keyof V[K]> | StringToBoolean<keyof V[K]>[];
} & ClassProp
>;
type TVDefaultVariants<V extends TVVariants<S>, S extends TVSlots, EV extends TVVariants<ES>, ES extends TVSlots> = {
[K in keyof V | keyof EV]?:
| (K extends keyof V ? StringToBoolean<keyof V[K]> : never)
| (K extends keyof EV ? StringToBoolean<keyof EV[K]> : never);
};Install with Tessl CLI
npx tessl i tessl/npm-tailwind-variants