@codefast/tailwind-variants
v0.3.10
Published
Tailwind CSS variants utilities with enhanced functionality and advanced type safety
Maintainers
Readme
Tailwind Variants
Type-safe variant API for Tailwind CSS with enhanced functionality and advanced TypeScript support for building flexible component styling systems.
Why @codefast/tailwind-variants?
This library is a high-performance, drop-in replacement for tailwind-variants with the same API but significantly better performance.
Performance Comparison
Benchmarked on Apple M-series chip. Results show operations per second (higher is better):
| Benchmark | @codefast/tailwind-variants | tailwind-variants | class-variance-authority | Speedup | | --------------------------- | :-------------------------: | :---------------: | :----------------------: | :-------------: | | Simple Variants | 477.8K ops/s 🥇 | 71.6K ops/s | 356.1K ops/s | 6.7x faster | | Simple + Merge | 280.7K ops/s 🥇 | 66.3K ops/s | 225.4K ops/s | 4.2x faster | | Complex Variants | 301.1K ops/s 🥇 | 48.8K ops/s | - | 6.2x faster | | Slots | 53.7K ops/s 🥇 | 10.9K ops/s | - | 4.9x faster | | Compound Slots | 31.3K ops/s 🥇 | 6.9K ops/s | - | 4.5x faster | | Extreme (240+ variants) | 10.6K ops/s 🥇 | 2.6K ops/s | - | 4.1x faster |
Note: CVA doesn't support slots, compound slots, or extends - it's only included in simple variant benchmarks.
Feature Comparison
| Feature | @codefast/tailwind-variants | tailwind-variants | CVA | | ------------------ | :-------------------------: | :---------------: | :-------: | | Basic Variants | ✅ | ✅ | ✅ | | Boolean Variants | ✅ | ✅ | ✅ | | Compound Variants | ✅ | ✅ | ✅ | | Default Variants | ✅ | ✅ | ✅ | | Slots | ✅ | ✅ | ❌ | | Compound Slots | ✅ | ✅ | ❌ | | Extends | ✅ | ✅ | ❌ | | Tailwind Merge | ✅ Built-in | ✅ Built-in | ❌ Manual | | TypeScript | ✅ Full | ✅ Full | ✅ Full | | Performance | ⚡ Fastest | 🐢 Slowest | 🏃 Medium | | Bundle Size | ~3KB | ~4KB | ~1KB |
Migration from tailwind-variants
Migrating is as simple as changing your import:
- import { tv } from "tailwind-variants";
+ import { tv } from "@codefast/tailwind-variants";That's it! The API is 100% compatible. All your existing code will work without any changes.
Installation
Install the package via pnpm (recommended):
pnpm add @codefast/tailwind-variantsOr using npm:
npm install @codefast/tailwind-variantsPeer Dependencies:
The library works with Tailwind CSS (optional but recommended):
pnpm add tailwindcssDependencies:
The library uses these runtime dependencies:
clsx: Utility for constructing className strings conditionallytailwind-merge: Utility for merging Tailwind CSS classes and resolving conflicts
Requirements:
- Node.js version 20.0.0 or higher
- TypeScript version 5.9.2 or higher (recommended)
- Tailwind CSS version 4.0.0 or higher (optional)
Quick Start
import { tv } from '@codefast/tailwind-variants';
const button = tv({
base: 'inline-flex items-center justify-center rounded-md font-medium transition-colors',
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
},
size: {
sm: 'h-9 px-3 text-sm',
md: 'h-10 px-4',
lg: 'h-11 px-8',
},
},
defaultVariants: {
variant: 'default',
size: 'md',
},
});
// Usage
console.log(button());
// Output: "inline-flex items-center justify-center rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4"
console.log(button({ variant: 'destructive', size: 'lg' }));
// Output: "inline-flex items-center justify-center rounded-md font-medium transition-colors bg-destructive text-destructive-foreground hover:bg-destructive/90 h-11 px-8"Usage
Core Features
The library provides a comprehensive set of features for variant-based styling:
Variant System
- Basic Variants: Define component variations with different styling options
- Boolean Variants: Support for boolean-based variant conditions
- Default Variants: Set default values for variant properties
- Nested Arrays: Support for nested array structures in class definitions
Advanced Features
- Slots: Multi-part component styling with individual slot control
- Compound Variants: Apply styles when multiple variant conditions are met
- Compound Slots: Apply styles to specific slots based on variant conditions
- Configuration Extension: Extend and override existing variant configurations
Developer Experience
- Type Safety: Full TypeScript support with intelligent type inference
- Tailwind Merge: Built-in conflict resolution for Tailwind CSS classes
- Performance: Optimized for minimal runtime overhead
- Flexibility: Support for custom class merging and configuration
Basic Variants
Create variant functions with different styling options:
import { tv } from '@codefast/tailwind-variants';
const button = tv({
base: 'inline-flex items-center justify-center rounded-md font-medium transition-colors',
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
},
size: {
sm: 'h-9 px-3 text-sm',
md: 'h-10 px-4',
lg: 'h-11 px-8',
},
},
defaultVariants: {
variant: 'default',
size: 'md',
},
});
// Usage examples
console.log(button());
// Output: "inline-flex items-center justify-center rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4"
console.log(button({ variant: 'destructive', size: 'lg' }));
// Output: "inline-flex items-center justify-center rounded-md font-medium transition-colors bg-destructive text-destructive-foreground hover:bg-destructive/90 h-11 px-8"
console.log(button({ variant: 'outline', size: 'sm' }));
// Output: "inline-flex items-center justify-center rounded-md font-medium transition-colors border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-3 text-sm"
// Add custom classes
console.log(button({ className: 'w-full' }));
// Output: "inline-flex items-center justify-center rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 w-full"Slots - Multi-part Components
Slots enable styling for components with multiple parts:
import { tv } from '@codefast/tailwind-variants';
const card = tv({
slots: {
base: 'rounded-lg border bg-card text-card-foreground shadow-sm',
header: 'flex flex-col space-y-1.5 p-6',
content: 'p-6 pt-0',
footer: 'flex items-center p-6 pt-0',
},
variants: {
variant: {
default: '',
destructive: {
base: 'border-destructive',
header: 'text-destructive',
},
},
},
});
// Usage
const cardStyles = card();
console.log(cardStyles.base());
// Output: "rounded-lg border bg-card text-card-foreground shadow-sm"
console.log(cardStyles.header());
// Output: "flex flex-col space-y-1.5 p-6"
console.log(cardStyles.content());
// Output: "p-6 pt-0"
console.log(cardStyles.footer());
// Output: "flex items-center p-6 pt-0"
// With variant
const destructiveCard = card({ variant: 'destructive' });
console.log(destructiveCard.base());
// Output: "rounded-lg border bg-card text-card-foreground shadow-sm border-destructive"
console.log(destructiveCard.header());
// Output: "flex flex-col space-y-1.5 p-6 text-destructive"Compound Variants
Apply styles when multiple variant conditions are met:
import { tv } from '@codefast/tailwind-variants';
const alert = tv({
base: 'relative w-full rounded-lg border px-4 py-3',
variants: {
variant: {
default: 'bg-background text-foreground',
destructive: 'border-destructive/50 text-destructive',
},
size: {
sm: 'text-sm',
md: 'text-base',
},
},
compoundVariants: [
{
variant: 'destructive',
size: 'md',
className: 'font-semibold', // Only applies when variant=destructive AND size=md
},
],
defaultVariants: {
variant: 'default',
size: 'md',
},
});
// Usage examples
console.log(alert({ size: 'sm' }));
// Output: "relative w-full rounded-lg border px-4 py-3 bg-background text-foreground text-sm"
console.log(alert({ variant: 'destructive', size: 'md' }));
// Output: "relative w-full rounded-lg border px-4 py-3 border-destructive/50 text-destructive text-base font-semibold"
console.log(alert({ variant: 'destructive', size: 'sm' }));
// Output: "relative w-full rounded-lg border px-4 py-3 border-destructive/50 text-destructive text-sm"Boolean Variants
Support for boolean-based variant conditions:
import { tv } from '@codefast/tailwind-variants';
const toggle = tv({
base: 'inline-flex items-center justify-center rounded-md text-sm font-medium',
variants: {
pressed: {
true: 'bg-accent text-accent-foreground',
false: 'bg-transparent',
},
disabled: {
true: 'opacity-50 pointer-events-none',
false: '',
},
},
defaultVariants: {
pressed: false,
disabled: false,
},
});
// Usage with boolean values
console.log(toggle());
// Output: "inline-flex items-center justify-center rounded-md text-sm font-medium bg-transparent"
console.log(toggle({ pressed: true }));
// Output: "inline-flex items-center justify-center rounded-md text-sm font-medium bg-accent text-accent-foreground"
console.log(toggle({ disabled: true }));
// Output: "inline-flex items-center justify-center rounded-md text-sm font-medium bg-transparent opacity-50 pointer-events-none"
console.log(toggle({ pressed: true, disabled: true }));
// Output: "inline-flex items-center justify-center rounded-md text-sm font-medium bg-accent text-accent-foreground opacity-50 pointer-events-none"Configuration Extension
Extend existing configurations for reusability:
import { tv } from '@codefast/tailwind-variants';
// Base button configuration
const baseButton = tv({
base: 'inline-flex items-center justify-center rounded-md font-medium',
variants: {
size: {
sm: 'h-9 px-3 text-sm',
md: 'h-10 px-4',
},
},
defaultVariants: {
size: 'md',
},
});
// Extended icon button
const iconButton = tv({
extend: baseButton,
base: 'aspect-square', // Additional base classes
variants: {
variant: {
// New variant options
ghost: 'hover:bg-accent hover:text-accent-foreground',
outline: 'border border-input',
},
},
defaultVariants: {
variant: 'ghost',
},
});
// Usage
console.log(baseButton());
// Output: "inline-flex items-center justify-center rounded-md font-medium h-10 px-4"
console.log(iconButton());
// Output: "inline-flex items-center justify-center rounded-md font-medium aspect-square h-10 px-4 hover:bg-accent hover:text-accent-foreground"
console.log(iconButton({ variant: 'outline', size: 'sm' }));
// Output: "inline-flex items-center justify-center rounded-md font-medium aspect-square h-9 px-3 text-sm border border-input"Advanced Features
Compound Slots
Apply styles to specific slots based on variant conditions:
import { tv } from '@codefast/tailwind-variants';
const dialog = tv({
slots: {
overlay: 'fixed inset-0 bg-background/80 backdrop-blur-sm',
content: 'fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2',
header: 'flex flex-col space-y-1.5 text-center sm:text-left',
footer: 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
},
variants: {
size: {
sm: '',
lg: '',
},
},
compoundSlots: [
{
size: 'sm',
slots: ['content'],
className: 'max-w-md',
},
{
size: 'lg',
slots: ['content'],
className: 'max-w-2xl',
},
],
});
// Usage
const smallDialog = dialog({ size: 'sm' });
console.log(smallDialog.content());
// Output: "fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 max-w-md"
const largeDialog = dialog({ size: 'lg' });
console.log(largeDialog.content());
// Output: "fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 max-w-2xl"Global Configuration with createTV
Create a factory with global configuration settings:
import { createTV } from '@codefast/tailwind-variants';
// Create factory with global configuration
const { tv, cn } = createTV({
twMerge: true,
twMergeConfig: {
extend: {
classGroups: {
// Custom class groups for better conflict resolution
'font-size': ['text-custom-sm', 'text-custom-lg'],
},
},
},
});
// Use tv with global configuration
const button = tv({
base: 'px-4 py-2 rounded',
variants: {
variant: {
primary: 'bg-blue-500 text-white',
secondary: 'bg-gray-500 text-white',
},
},
});
// Use cn utility with global configuration
const classes = cn('px-4 py-2', 'px-6 py-3'); // Tailwind merge resolves conflicts
console.log(classes);
// Output: "px-6 py-3" (px-6 overrides px-4, py-3 overrides py-2)Nested Arrays Support
Support for nested array structures in class definitions:
import { tv } from '@codefast/tailwind-variants';
const component = tv({
base: ['base-class-1', ['base-class-2', ['base-class-3', 'base-class-4']]], // Automatically flattened
variants: {
variant: {
primary: ['text-blue-500', ['bg-blue-50', ['hover:bg-blue-100', 'focus:ring-blue-200']]],
},
},
});API Reference
tv(config, options?)
Creates a variant function from configuration.
Parameters:
config: Configuration object defining variants, slots, and stylingoptions?: Optional Tailwind Variants configuration for customization
Returns: Configured variant function
createTV(globalConfig?)
Creates a factory with global configuration settings.
Parameters:
globalConfig?: Global Tailwind Variants configuration applied to all instances
Returns: Object containing tv and cn functions with global settings
cn(...classes)
Combines and merges CSS classes using tailwind-merge for conflict resolution.
Parameters:
...classes: CSS classes to combine and merge
Returns: Merged class string with conflicts resolved
cx(...classes)
Combines CSS classes using clsx without conflict resolution.
Parameters:
...classes: CSS classes to combine
Returns: Combined class string without merging
VariantProps
Extracts variant props type from a variant function for TypeScript integration.
Type Parameter:
T: Variant function type
Returns: Props type object for component integration
TypeScript Integration
The library provides comprehensive TypeScript support:
import { tv, type VariantProps } from "@codefast/tailwind-variants";
const button = tv({
base: "px-4 py-2 rounded",
variants: {
variant: {
primary: "bg-blue-500 text-white",
secondary: "bg-gray-500 text-white",
},
size: {
sm: "text-sm",
lg: "text-lg",
},
},
});
// Extract props type
type ButtonProps = VariantProps<typeof button>;
// Type: { variant?: "primary" | "secondary"; size?: "sm" | "lg"; className?: string; }
// Usage in React components
interface MyButtonProps extends ButtonProps {
children: React.ReactNode;
onClick?: () => void;
}
const MyButton: React.FC<MyButtonProps> = ({
variant,
size,
className,
children,
onClick
}) => {
return (
<button
className={button({ variant, size, className })}
onClick={onClick}
>
{children}
</button>
);
};Slots Type Safety
Slots provide full type safety for multi-part components:
import { tv } from '@codefast/tailwind-variants';
const card = tv({
slots: {
base: 'rounded-lg border',
header: 'p-4 border-b',
content: 'p-4',
},
variants: {
variant: {
default: '',
elevated: {
base: 'shadow-lg',
header: 'bg-muted',
},
},
},
});
// Type inference for slots
const styles = card({ variant: 'elevated' });
// styles.base() - available
// styles.header() - available
// styles.content() - available
// styles.nonExistent() - TypeScript error!Configuration Options
Customize Tailwind merge behavior and other settings:
import { tv } from '@codefast/tailwind-variants';
const component = tv(
{
base: 'px-4 py-2',
variants: {
size: {
sm: 'px-2 py-1',
lg: 'px-6 py-3',
},
},
},
{
twMerge: true, // Enable/disable tailwind merge (default: true)
twMergeConfig: {
extend: {
classGroups: {
// Custom class groups for better conflict resolution
'font-size': ['text-custom-sm', 'text-custom-lg'],
},
},
},
},
);Disable Tailwind Merge
In some cases, you may want to disable Tailwind merge:
import { tv } from '@codefast/tailwind-variants';
const component = tv(
{
base: 'px-4 py-2',
variants: {
size: {
sm: 'px-2 py-1', // Will not merge with base classes
},
},
},
{
twMerge: false, // Disable tailwind merge
},
);
console.log(component({ size: 'sm' }));
// Output: "px-4 py-2 px-2 py-1" (both px classes will be kept)Utility Functions
cn Function
Combine and merge CSS classes with conflict resolution:
import { cn } from '@codefast/tailwind-variants';
// Basic usage
const classes = cn('px-4 py-2', 'bg-blue-500', 'text-white');
console.log(classes);
// Output: "px-4 py-2 bg-blue-500 text-white"
// With conflicting classes (tailwind-merge resolves conflicts)
const conflicting = cn('px-4 py-2', 'px-6 py-3');
console.log(conflicting);
// Output: "px-6 py-3" (later classes override earlier ones)
// With conditional classes
const conditional = cn('base-class', true && 'condition-true-class', false && 'condition-false-class', {
'object-condition': true,
});
console.log(conditional);
// Output: "base-class condition-true-class object-condition"cx Function
Combine CSS classes without conflict resolution:
import { cx } from '@codefast/tailwind-variants';
const classes = cx('px-4 py-2', 'px-6 py-3');
console.log(classes);
// Output: "px-4 py-2 px-6 py-3" (no conflict resolution)Best Practices
Component Library Pattern
Create consistent component libraries:
// components/button.ts
import { tv, type VariantProps } from "@codefast/tailwind-variants";
export const buttonVariants = tv({
base: "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "underline-offset-4 hover:underline text-primary",
},
size: {
default: "h-10 py-2 px-4",
sm: "h-9 px-3 rounded-md",
lg: "h-11 px-8 rounded-md",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
});
export type ButtonProps = VariantProps<typeof buttonVariants>;
// components/Button.tsx (React component)
import React from "react";
import { buttonVariants, type ButtonProps } from "./button";
interface ButtonComponentProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
ButtonProps {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonComponentProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
return (
<button
className={buttonVariants({ variant, size, className })}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = "Button";
export { Button };Theme System Pattern
Create theme systems with global configuration:
// theme/variants.ts
import { createTV } from '@codefast/tailwind-variants';
// Global theme configuration
const { tv: themeTV, cn: themeCN } = createTV({
twMerge: true,
twMergeConfig: {
extend: {
classGroups: {
// Theme-specific class groups
'theme-color': ['theme-primary', 'theme-secondary', 'theme-accent'],
},
},
},
});
// Export themed utilities
export { themeTV as tv, themeCN as cn };
// components/themed-button.ts
import { tv } from '../theme/variants';
export const themedButton = tv({
base: 'inline-flex items-center justify-center rounded-md font-medium',
variants: {
theme: {
light: 'bg-white text-black border border-gray-200',
dark: 'bg-gray-900 text-white border border-gray-700',
},
variant: {
primary: 'theme-primary', // Handled by custom class group
secondary: 'theme-secondary',
},
},
});Responsive Design Pattern
Use with responsive classes for mobile-first design:
import { tv } from '@codefast/tailwind-variants';
const responsiveCard = tv({
base: 'rounded-lg border bg-card text-card-foreground shadow-sm',
variants: {
size: {
sm: 'p-4 sm:p-6',
md: 'p-6 sm:p-8 lg:p-10',
lg: 'p-8 sm:p-10 lg:p-12 xl:p-16',
},
layout: {
stack: 'flex flex-col space-y-4',
grid: 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4',
},
},
compoundVariants: [
{
size: 'lg',
layout: 'grid',
className: 'lg:grid-cols-4 xl:grid-cols-5',
},
],
defaultVariants: {
size: 'md',
layout: 'stack',
},
});Performance Optimization
Optimize for performance:
// ✓ Good: Define variants outside component
const buttonVariants = tv({
base: "px-4 py-2 rounded",
variants: {
variant: {
primary: "bg-blue-500 text-white",
secondary: "bg-gray-500 text-white",
},
},
});
const Button = ({ variant, className, ...props }) => {
return (
<button
className={buttonVariants({ variant, className })}
{...props}
/>
);
};
// ✗ Bad: Define variants inside component (recreated on every render)
const Button = ({ variant, className, ...props }) => {
const buttonVariants = tv({
base: "px-4 py-2 rounded",
variants: {
variant: {
primary: "bg-blue-500 text-white",
secondary: "bg-gray-500 text-white",
},
},
});
return (
<button
className={buttonVariants({ variant, className })}
{...props}
/>
);
};Performance Considerations
Bundle Size
The library is optimized for minimal bundle impact:
- Tree-shakeable exports for unused functionality
- Minimal runtime dependencies
- Efficient class processing algorithms
Runtime Performance
- Lazy evaluation of variant resolution
- Efficient class merging algorithms
- Minimal memory footprint
- Cached tailwind-merge instances for reuse
Migration Guide
From class-variance-authority (cva)
Migrating from other variant libraries:
// Before (cva)
import { cva } from 'class-variance-authority';
const button = cva('px-4 py-2 rounded', {
variants: {
variant: {
primary: 'bg-blue-500 text-white',
secondary: 'bg-gray-500 text-white',
},
},
});
// After (@codefast/tailwind-variants)
import { tv } from '@codefast/tailwind-variants';
const button = tv({
base: 'px-4 py-2 rounded',
variants: {
variant: {
primary: 'bg-blue-500 text-white',
secondary: 'bg-gray-500 text-white',
},
},
});Key Differences
- API Structure: Uses
baseproperty instead of first string parameter - Slots Support: Native support for multi-part components
- Extended Configuration: Built-in support for configuration extension
- Enhanced TypeScript: Improved type inference and safety
- Tailwind Merge: Built-in conflict resolution
Contributing
We welcome all contributions! To get started with development:
Environment Setup
- Fork this repository
- Clone to your machine:
git clone <your-fork-url> - Install dependencies:
pnpm install - Create a new branch:
git checkout -b feature/feature-name
Development Workflow
# Build all packages
pnpm build:packages
# Development mode for tailwind-variants
pnpm dev --filter=@codefast/tailwind-variants
# Run tests
pnpm test --filter=@codefast/tailwind-variants
# Run tests with coverage
pnpm test:coverage --filter=@codefast/tailwind-variants
# Lint and format
pnpm lint:fix
pnpm formatAdding New Features
- Implement feature in
src/directory - Add comprehensive tests in
tests/directory - Update TypeScript types as needed
- Update documentation
- Submit a pull request
License
Distributed under the MIT License. See LICENSE for more details.
Contact
- npm: @codefast/tailwind-variants
- GitHub: codefastlabs/codefast
- Issues: GitHub Issues
- Documentation: Component Docs
Acknowledgments
This library is built on top of excellent open-source projects:
- clsx - Utility for constructing className strings
- tailwind-merge - Utility for merging Tailwind CSS classes
- Tailwind CSS - Utility-first CSS framework
Changelog
See CHANGELOG.md for a complete list of changes and version history.
