react-tailwind-variants
Version:
React Stitches-like variants API for tailwindcss classes
540 lines (437 loc) • 12 kB
Markdown
# React Tailwind Variants
[![npm package][npm-img]][npm-url]
[![npm bundle size][bundle-size-img]][bundle-size-url]
[![Downloads][downloads-img]][downloads-url]
`Stitches.js`-like API for creating composable components. You can define a single variant, multiple variants, and even compound variants which allow you to define classes based on a combination of variants.
This is a modified version of the [`classname-variants`](https://github.com/fgnass/classname-variants/)
## Features
- 📦 Lightweight
- 📜 Fully type-safe
- 💅🏼 Elegant [Stitches-like](https://github.com/stitchesjs/stitches) variants API
- 🗑️ Automatic tailwindcss classes conflict resolution via [tailwind-merge](https://github.com/dcastil/tailwind-merge)
- ♻️ Polymorphic components via [@radix-ui/slot](https://www.radix-ui.com/docs/primitives/utilities/slot)
## Installation
```bash
npm install tailwind-merge react-tailwind-variants
```
## Usage
- [Basics](#basics)
- [Boolean variants](#boolean-variants)
- [Compound variants](#compound-variants)
- [Default variants](#default-variants)
- [Polymorphic components](#polymorphic-components)
- [Composing components](#composing-components)
- [Utilities](#utilities)
- [variants(config)](#variantsconfig)
- [variantProps(config)](#variantpropsconfig)
- [extractVariantsConfig(component)](#extractvariantsconfigcomponent)
- [Typescript utilities](#typescript-utilities)
- [VariantsConfigOf\<Component\>](#variantsconfigofcomponent)
- [VariantPropsOf\<Component\>](#variantpropsofcomponent)
- [Tailwind CSS IntelliSense](#tailwind-css-intellisense)
### Basics
Let's assume we want to build a button component with Tailwind CSS that comes in different sizes and colors.
It consists of some _base classes_ that are always present as well as some optional classes that need to be added depending on the desired _variants_.
```tsx
import { styled } from 'react-tailwind-variants';
const Button = styled('button', {
base: 'rounded text-white',
variants: {
color: {
brand: 'bg-sky-500',
accent: 'bg-teal-500',
},
size: {
small: 'px-5 py-3 text-xs',
large: 'px-6 py-4 text-base',
},
},
});
```
The result is a react component:
```tsx
<Button type="submit" color="brand" size="large" className="px-8">
Click me!
</Button>
```
Component will be rendered as:
```html
<button
type="submit"
className="rounded text-white bg-sky-500 py-4 text-base px-8"
>
Click me!
</button>
```
As a value for classes, you can use a `"string"`, an `"array"` of strings, or `"null"`:
```tsx
import { styled } from 'react-tailwind-variants';
const Button = styled('button', {
base: ['rounded', 'text-white'],
variants: {
color: {
none: null,
brand: 'bg-sky-500',
accent: 'bg-teal-500',
},
},
});
```
### Boolean variants
Variants can be of type `boolean` by using `"true"` or/and `"false"` as the key:
```tsx
import { styled } from 'react-tailwind-variants';
const Button = styled('button', {
base: 'text-white',
variants: {
rounded: {
true: 'rounded-full',
},
},
});
```
### Compound variants
The `compoundVariants` option can be used to apply class names based on a combination of other variants.
```tsx
import { styled } from 'react-tailwind-variants';
const Button = styled('button', {
base: 'text-base'
variants: {
variant: {
none: null,
filled: 'bg-blue-500 text-white',
outlined: 'border border-blue-500 text-blue-500',
plain: 'bg-transparent text-blue-500',
},
size: {
sm: 'px-3 py-1.5'
md: 'px-4 py-2'
lg: 'px-6 py-3'
},
},
compoundVariants: [
{
variants: {
variant: ['filled', 'outlined'],
size: 'sm'
},
className: 'text-sm'
},
{
// `compoundVariants` className takes
// precedence over `variants`,
// so in this case the class `p-0`
// will override `padding` classes
variants: {
variant: 'none'
},
className: 'p-0'
},
],
});
```
```tsx
<Button variant="outlined" size="sm">Outlined button</Button>
<Button variant="none" size="sm">Unstyled button</Button>
```
will be rendered as:
```html
<button
class="text-base border border-blue-500 text-blue-500 px-3 py-1.5 text-sm"
>
Outlined button
</button>
<button class="text-base p-0">Unstyled button</button>
```
### Default variants
The `defaultVariants` option can be used to select a variant by default.
All non-boolean variants for which no default values are specified are required.
If no default value is specified for boolean options, it evaluates to "false"
Below is an example with a component that has a required `size` and an optional `color` variants
```tsx
import { styled } from 'react-tailwind-variants';
const Button = styled('button', {
variants: {
color: {
brand: 'bg-sky-500',
accent: 'bg-teal-500',
},
size: {
small: 'px-5 py-3 text-xs',
large: 'px-6 py-4 text-base',
},
elevated: {
true: 'shadow',
},
},
defaultVariants: {
color: 'neutral',
},
});
```
### Polymorphic components
If you want to keep all the variants you have defined for a component but want to render a different HTML tag or a different custom component, you can use the `"asChild"` prop to do so:
```tsx
import { styled } from 'react-tailwind-variants';
const Button = styled('button', {
base: 'rounded text-white',
variants: {
color: {
brand: 'bg-sky-500',
accent: 'bg-teal-500',
},
size: {
small: 'px-5 py-3 text-xs',
large: 'px-6 py-4 text-base',
},
},
});
```
```tsx
<Button asChild color="brand" size="large">
<a href="/test" className="mt-4 mb-2">
Button as link
</a>
</Button>
```
will be rendered as:
```html
<a
href="/test"
className="rounded text-white bg-sky-500 px-6 py-4 text-base mt-4 mb-2"
>
Button as link
</a>
```
### Composing components
Composing one styled component into another.
1. Components can be composed via the `styled` function.
```tsx
import { styled } from 'react-tailwind-variants';
const BaseButton = styled('button', {
base: 'text-center bg-blue-500 text-white',
variants: {
size: {
small: 'px-5 py-3 text-xs',
large: 'px-6 py-4 text-base',
},
},
});
const Button = styled(BaseButton, {
base: 'rounded text-white',
variants: {
color: {
brand: 'bg-sky-500',
accent: 'bg-teal-500',
},
},
});
```
```tsx
<Button type="submit" color="brand" size="large">
Click me!
</Button>
```
will be rendered as:
```html
<button
type="submit"
className="text-center text-white px-6 py-4 text-base rounded text-white bg-sky-500"
>
Click me!
</button>
```
2. You can also achieve the same result using `"asChild"` prop:
```tsx
import { styled } from 'react-tailwind-variants';
const BaseButton = styled('button', {
base: 'text-center bg-blue-500 text-white',
variants: {
size: {
small: 'px-5 py-3 text-xs',
large: 'px-6 py-4 text-base',
},
},
});
const Button = styled('button', {
base: 'rounded text-white',
variants: {
color: {
brand: 'bg-sky-500',
accent: 'bg-teal-500',
},
},
});
```
```tsx
<BaseButton asChild size="large">
<Button type="submit" color="brand">
Click me!
</Button>
</Button>
```
will be rendered as:
```html
<button
type="submit"
className="text-center text-white px-6 py-4 text-base rounded text-white bg-sky-500"
>
Click me!
</button>
```
### Utilities
#### `variants(config)`
The function accepts variants config as argument and returns a `className` builder function
```ts
import { variants } from 'react-tailwind-variants';
const buttonVariants = variants({
base: 'rounded text-white',
variants: {
color: {
brand: 'bg-sky-500',
accent: 'bg-teal-500',
},
size: {
small: 'px-5 py-3 text-xs',
large: 'px-6 py-4 text-base',
},
},
});
console.log(
buttonVariants({
color: 'brand',
size: 'small',
className: 'text-sky-900 px-8',
})
);
// Console output:
// 'rounded bg-sky-500 py-3 text-xs text-sky-900 px-8'
```
#### `variantProps(config)`
The function accepts variants config as argument and returns props builder function
```ts
import { variantProps } from 'react-tailwind-variants';
const buttonVariantProps = variantProps({
base: 'rounded text-white',
variants: {
color: {
brand: 'bg-sky-500',
accent: 'bg-teal-500',
},
size: {
small: 'px-5 py-3 text-xs',
large: 'px-6 py-4 text-base',
},
},
});
console.log(
buttonVariantProps({
color: 'brand',
size: 'small',
className: 'text-sky-900 px-8',
type: 'button',
onClick: e => {
// ...
},
})
);
// Console output:
// {
// className: 'rounded bg-sky-500 py-3 text-xs text-sky-900 px-8'
// type: "button",
// onClick: ...
// }
```
#### `extractVariantsConfig(component)`
The function accepts a component from which it extracts the configuration of variants
```ts
import { styled, extractVariantsConfig } from 'react-tailwind-variants';
const Button = styled('button', {
base: ['rounded', 'text-white'],
variants: {
color: {
none: null,
brand: 'bg-sky-500',
accent: 'bg-teal-500',
},
},
});
console.log(extractVariantsConfig(Button));
// Console output:
// {
// base: ['rounded', 'text-white'],
// variants: {
// color: {
// none: null,
// brand: 'bg-sky-500',
// accent: 'bg-teal-500',
// },
// },
// }
```
### Typescript utilities
#### `VariantsConfigOf<Component>`
A utility that allows you to extract the configuration type from the component type
```ts
import { type VariantsConfigOf, styled } from 'react-tailwind-variants';
const Button = styled('button', {
base: ['rounded', 'text-white'],
variants: {
color: {
none: null,
brand: 'bg-sky-500',
accent: 'bg-teal-500',
},
},
});
type ButtonVariantsConfig = VariantsConfigOf<typeof Button>;
```
#### `VariantPropsOf<Component>`
A utility that allows you to extract the variant props type from the component type
```ts
import { type VariantPropsOf, styled } from 'react-tailwind-variants';
const Button = styled('button', {
base: ['rounded', 'text-white'],
variants: {
color: {
none: null,
brand: 'bg-sky-500',
accent: 'bg-teal-500',
},
},
});
type ButtonVariantProps = VariantPropsOf<typeof Button>;
```
### Tailwind CSS IntelliSense
In order to get auto-completion for the CSS classes themselves, you can use the [Tailwind CSS IntelliSense](https://github.com/tailwindlabs/tailwindcss-intellisense) plugin for VS Code. In order to make it recognize the strings inside your variants-config, you have to somehow mark them and configure the plugin accordingly.
One way of doing so is by using tagged template literals:
```ts
import { styled, tw } from 'react-tailwind-variants';
const Button = styled('button', {
base: tw`px-5 py-2 text-white`,
variants: {
color: {
neutral: tw`bg-slate-500 hover:bg-slate-400`,
accent: tw`bg-teal-500 hover:bg-teal-400`,
},
},
});
```
You can then add the following line to your `settings.json`:
```
"tailwindCSS.experimental.classRegex": ["tw`(\\`|[^`]+?)`"]
```
> **Note**
> The `tw` helper function is just an alias for [`String.raw`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw).
[npm-img]: https://img.shields.io/npm/v/react-tailwind-variants
[npm-url]: https://www.npmjs.com/package/react-tailwind-variants
[bundle-size-img]: https://img.shields.io/bundlephobia/minzip/react-tailwind-variants
[bundle-size-url]: https://bundlephobia.com/package/react-tailwind-variants
[downloads-img]: https://img.shields.io/npm/dt/react-tailwind-variants
[downloads-url]: https://www.npmtrends.com/react-tailwind-variants