better-variants
v0.2.2
Published
A lightweight utility for working with CSS classes and variants.
Maintainers
Readme
better-variants
A lightweight utility for working with CSS classes and variants.
Why does this exist?
If you're using Tailwind CSS, you’ve likely used clsx, tailwind-merge, or class-variance-authority (cva) as well. Possibly all three, given the popularity of shadcn/ui.
These tools have been serving me well, but I couldn't help thinking about simplifying them for a better DX and a leaner bundle size. That's the aim of better-variants: unify the strengths of clsxand cva with sensible constraints and streamlined API.
Features
- Conditional Class Name Composition: Merge classes conditionally, similar to
clsx, with support for strings, objects, booleans, and nullish values - Variant Class Management: Define and apply variants (e.g., styles for different states or sizes) with a simple, type-safe API inspired by
cva - Type Safety: Use TypeScript generics to ensure props match your config, providing auto-completion and error checking.
- Required Defaults: Variants and sizes must include a
defaultkey, ensuring predictable fallbacks without optional props.
Installation
npm install better-variants
# or
pnpm add better-variants
# or
yarn add better-variantsUsage
Merging Classes with cx
Use cx to combine classes conditionally:
const classes = cx("bg-gray-200", { "text-white": true }, "px-4", null);
// -> "bg-gray-200 text-white px-4"Creating Variants with createVariants
Define a config object with base, variant, and size properties:
const button = createVariants({
base: "inline-flex items-center justify-center rounded text-sm",
variant: {
default: "bg-gray-200 text-black",
primary: "bg-blue-500 text-white",
destructive: "bg-red-600 text-white",
},
size: {
default: "h-9 px-4",
sm: "h-8 px-3 text-xs",
},
});Apply the variant function with optional props:
// Uses defaults
const defaultClasses = button();
// -> "inline-flex items-center justify-center rounded text-sm bg-gray-200 text-black h-9 px-4"
// Custom variant and size
button({ variant: "primary", size: "sm" });
// -> "inline-flex items-center justify-center rounded text-sm bg-blue-500 text-white h-8 px-3 text-xs"
// Only custom variant (uses default size)
button({ variant: "primary" });
// -> "inline-flex items-center justify-center rounded text-sm bg-blue-500 text-white h-9 px-4"
// Only custom size (uses default variant)
button({ size: "sm" });
// -> "inline-flex items-center justify-center rounded text-sm bg-gray-200 text-black h-8 px-3 text-xs"Type-Safe Props
The VariantProps utility extracts the expected prop types from your config:
import { type VariantProps } from "better-variants";
type ButtonProps = VariantProps<typeof button>;
// type ButtonProps = { variant?: "default" | "primary" | "destructive"; size?: "default" | "sm" }
Button Component Example
import { createVariants, type VariantProps } from "better-variants";
import { cn } from "@/lib/utils";
const buttonVariants = createVariants({
base: "inline-flex items-center justify-center rounded text-sm font-medium transition-colors focus:outline-none",
variant: {
default: "bg-gray-200 text-gray-900 hover:bg-gray-300",
primary: "bg-blue-500 text-white hover:bg-blue-600",
destructive: "bg-red-500 text-white hover:bg-red-600",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-8 px-3 text-sm",
},
});
type ButtonProps = React.ComponentProps<"button"> & VariantProps<typeof buttonVariants>;
const Button = ({ className, variant, size, children, ...props }: ButtonProps) => {
return (
<button className={cn(buttonVariants({ variant, size }), className)} {...props}>
{children}
</button>
);
};
export { Button, buttonVariants };Usage:
<Button variant="primary" size="sm">Submit</Button>API Reference
cx(...inputs: ClassValue[])
- Parameters: Variable number of
string,Record<string, boolean>,boolean,null,undefined. - Returns: A string of space-separated class names.
- Purpose: Merges classes, ignoring falsy or non-true values.
createVariants<T extends TConfig>(config: T)
- Parameters:
config: An object withbase,variant, andsizeproperties.base: Classes that will always be applied.variant: Variant-specific classes. Must include a 'default' which is used as a fallback.size: Size-specific classes. Must include a 'default' which is used as a fallback.
- Returns: A function that optionally takes
variantandsizeprops and returns a combined class string. - Purpose: Generates a variant selection function with type-safe props.
VariantProps<T>
- Parameters: A function returned by
createVariants - Returns: The inferred
variantandsizetypes - Purpose: Type helper for component prop definitions
Limitations
- ❌ No compound variants supported (e.g.,
variant=primary+size=lg→ add special class) - 🔒 Configuration is limited to
base,variant, andsize(no arbitrary keys for now)
Contributing
Contributions are welcome! If you:
- Found a bug
- Have a feature request
- Want to improve the docs
- Or just feel like helping out
Feel free to open an issue or submit a PR.
Before contributing, please:
- Check open issues and discussions to avoid duplicates
- Keep PRs focused and minimal
- Follow existing coding and documentation style
Thanks for helping make better-variants better!
