Transform component props to classnames
npm install variant-classnamesvariant-classnames is just like classnames, without boilerplate code. variant-classnames is unit tested and is ready for production use.If you're looking for a production-scale application that uses variant-classnames, please have a look at Testfully.
> The whole classname thing is popular in React eco-system, but it doesn't mean that you can't use variant-classnames with any other framework. We're framework/library agnostic.
``via npm
npm i variant-classnames
Usage
The
cn function receives two object arguments:
- variants: A map of prop values and their corresponding CSS classes
- props: Provided props to your component
Examples
Let's build a
component. It supports two colors (blue, red), two sizes (small, large), and a disabled state. We will use Tailwind CSS to style our components, but you can attach any CSS classes. To keep it simple, the component does not support default props. That said, variant-classnames works with default props as well.`tsx
import React from "react";
import cn from "variant-classnames";type ButtonProps = {
className?: string;
text: string;
color: "red" | "blue";
size: "small" | "large";
disabled?: boolean;
};
export const Button:React.FC = (props) => {
const variants = {
// class names for "color" variants
color: {
red: "bg-red-100 text-red", // <- will be applied to
blue: "bg-blue-100 text-blue", // <- will be applied to
},
// class names for "size" variants
size: {
small: "text-sm py-5 px-2", // <- will be applied to
large: "text-md py-8 px-4", // <- will be applied to
},
// class names for "disabled" variants
disabled: {
true: "cursor-not-allowed opacity-5", // <- will be applied to
},
};
const classes = cn(variants, props);
return
}
`Provided with
, the following CSS classes will be attached to :
`
color=red size=small
------------------- -----------------
bg-red-100 text-red text-sm py-5 px-2
`Provided with
, the following CSS classes will be attached to :
`
color=blue size=small disabled=true
--------------------- ----------------- ----------------------------
bg-blue-100 text-blue text-sm py-5 px-2 cursor-not-allowed opacity-5
`FAQ
_How do I join the value of the
className prop?_
cn will do the job. For example, transforms to:
`
color=blue size=small className
--------------------- ----------------- ----------
bg-blue-100 text-blue text-sm py-5 px-2 my-class
`
_How do I join a set of CSS classes to all variants?_
Use the
$all directive.`tsximport React from "react";
import cn from "variant-classnames";
type ButtonProps = {
text: string;
color: "red" | "blue";
};
export const Button:React.FC = (props) => {
const variants = {
$all: "py-4 px-2", // <- this will be always included
// class names for "color" variants
color: {
red: "bg-red-100 text-red", // <- will be applied to
blue: "bg-blue-100 text-blue", // <- will be applied to
},
};
const classes = cn(variants, props);
return
}
`
_Can I do nesting of variants?_
Yes, you can! Let's say the color of our button is different based on "disabled" state. We can write the following code:
`tsximport React from "react";
import cn from "variant-classnames";
type ButtonProps = {
text: string;
color: "red" | "blue";
disabled?: boolean;
};
export const Button:React.FC = (props) => {
const variants = {
$all: "py-4 px-2", // <- this will be always included
// class names for "color" variants
color: {
red: {
$all: "text-red", // <- this will be always included when is set
disabled: {
true: "bg-red-400", // <- this wil be applied to
false: "bg-red-200", // <- this wil be applied to
},
},
blue: {
$all: "text-blue", // <- this will be always included when is set
disabled: {
true: "bg-blue-400", // <- this wil be applied to
false: "bg-blue-200", // <- this wil be applied to
},
},
},
// class names for "disabled" variants
disabled: {
true: "cursor-not-allowed opacity-5", // <- will be applied to
},
};
const classes = cn(variants, props);
return
}
`
_Can I use cn with objects other than props?_
Yes, you can! The second argument can be anything, as long as it's an object.
Typings
You get types autocompletion when creating your variants object by importing either VariantsOf | VxOf types.$3
`ts
import vx, { VxOf } from "variant-classnames";const props = {
disabled: true,
};
const variants: VxOf = {
disabled: "when-disabled",
primary: "when-primary", // <-- ts will complain here as
primary does not exist in props
};console.log("-->", vx(variants, props)); // --> when-disabled
`$3
`ts
import vx, { VxOf } from "variant-classnames";type Props = {
disabled: boolean;
type: "primary" | "secondary";
color?: "blue" | "red";
};
const props: Props = {
disabled: true,
type: "primary",
color: "blue",
};
const variants: VxOf = {
$forward: "",
type: {
$notnil: "string",
$nil: "string",
primary: "when [type] is (primary)",
$none: {
color: {
blue: "when [type] is neither (primary | secondary) and [color] is (red)",
$none: "when [type] is neither (primary|secondary) and [color] is neither (red | blue)",
},
},
},
color: {
blue: {
disabled: {
true: "when [color] is (blue) and [disabled] is (true)",
},
},
},
};
console.log("-->", vx(variants, props)); // --> string when [type] is (primary) when [color] is (blue) and [disabled] is (true)
`Tailwind CSS IntelliSense in VSCode
Want to get auto complete when writing tailwind css classnames in VSCode? No problem!
$3
1. Install Tailwind CSS classRegex extension
2. Update your workspace settings (JSON)
`json
{
"tailwindCSS.classAttributes": ["class", "className", "ngClass"],
"tailwindCSS.experimental.classRegex": [
["vc\\(([^)])\\)", "]" class="text-primary hover:underline" target="_blank" rel="noopener noreferrer">\"'.*?[\"']"],
here please note that:
- classAttributes can be extended, allowing to add new selectors to get the autocompletion. in the case above we have added ngClass, hence now