A simple way to style React components.
- Quick Usage
- Features
- Documentation
- Merging TailwindCSS classes without conflict
- Contributing
- Changelog
npm install react-dye
import { dye } from 'react-dye'
// Create a styled button with a danger variant
const Button = dye('px-4 py-2 rounded font-medium', {
default: 'bg-blue-500 hover:bg-blue-600',
danger: 'bg-red-500 hover:bg-red-600'
}, 'button')
// Apply the same styles to a link
const ButtonLink = Button.as('a')
// Extend with additional classes and variants
const BigButton = Button.extend('text-3xl', {
success: 'bg-green-500 hover:bg-green-600',
})
// Use as normal component
function App() {
return (
<div>
<Button>Primary Action</Button> {/* applies `default` variant by default */}
<Button variant="danger">Danger Action</Button>
<BigButton variant="danger">Big Danger!</BigButton>
<ButtonLink href="#">Link</ButtonLink> {/* has the same props as the base element */}
</div>
)
}
- Zero dependencies.
- Lightweight (about 0.5kb gzipped).
- Create styled components with CSS classes (ideal when using TailwindCSS or similar).
- Use variants to create different styles for the same component.
- Extend existing components with additional classes/variants.
- Clone components with different element.
- Returns fully typed components.
This library exports two functions:
dye
: creates a styled component with CSS classes and variants.create_dye
: creates a newdye
function with custom config.
In its simplest form, dye
takes some CSS classes and an HTML tag, and returns a component that applies the CSS classes to the element.
import { dye } from 'react-dye'
const Title = dye('mb-2 text-2xl', 'h1')
<Title>content</Title>
will render
<h1 className="mb-2 text-2xl">content</h1>
The tag
is optional, and it defaults to div
.
const Section = dye('p-3 mb-4')
<Section>content</Section>
will render
<div className="p-3 mb-4">content</div>
You can pass additional CSS classes to the component using className
and they will be added
const Section = dye('p-3 mb-4')
<Section className="font-bold">content</Section>
will render
<div className="p-3 mb-4 font-bold">content</div>
Note that by default dye
will simply concatenate classes, if you are using TailwindCSS then check the Merging TailwindCSS classes without conflict guide below.
Sometimes you need to style a component differently depending on the state of the component. You can achieve this by passing an object as the second argument to dye
, where the keys are the variants and the values are the CSS classes.
const Button = dye('px-4 py-2 rounded', {
default: 'bg-blue-500 hover:bg-blue-600',
success: 'bg-green-500 hover:bg-green-600',
danger: 'bg-red-500 hover:bg-red-600',
}, 'button')
<Button>Default Button</Button>
<Button variant="success">Success Button</Button>
<Button variant="danger">Danger Button</Button>
will render
<button className="px-4 py-2 rounded bg-blue-500 hover:bg-blue-600">Default Button</button>
<button className="px-4 py-2 rounded bg-green-500 hover:bg-green-600">Success Button</button>
<button className="px-4 py-2 rounded bg-red-500 hover:bg-red-600">Danger Button</button>
When the variant
property is passed to the component, the corresponding classes are added to the element.
Note that the default
variant is applied by default if defined.
What if you styled an elememt, like the Button
above, and you want to apply the same styles to a different element? instead of duplicating the code, all components created by dye
have a method as()
that you can use to clone the component with a different tag.
import {dye} from 'react-dye'
const Button = dye('px-4 py-2 rounded', {
default: 'bg-blue-500 hover:bg-blue-600',
success: 'bg-green-500 hover:bg-green-600',
danger: 'bg-red-500 hover:bg-red-600',
}, 'button')
const ButtonLink = Button.as('a') // returns a new component that renders `a` instead of `button`
<ButtonLink variant="success" href="#">Link</ButtonLink>
will render
<a className="px-4 py-2 rounded bg-green-500 hover:bg-green-600" href="#">Link</a>
Same as .as()
, you may need to clone a styled component but with additional classes and/or variants. You can do that with the .extend()
method.
import {dye} from 'react-dye'
const Button = dye('px-4 py-2 rounded', {
default: 'bg-blue-500 hover:bg-blue-600',
success: 'bg-green-500 hover:bg-green-600',
danger: 'bg-red-500 hover:bg-red-600',
}, 'button')
const BigButton = Button.extend('text-3xl font-semibold') // adds new classes
const SpecialButton = Button.extend('', {
bonus: 'bg-orange-500 hover:bg-orange-600', // adds new variant
success: 'border-2 border-green-300', // adds classes to existing variant
})
<BigButton variant="danger">Stop</BigButton>
<SpecialButton variant="bonus">Bonus</SpecialButton>
<SpecialButton variant="success">Congratulations</SpecialButton>
will render
<button className="px-4 py-2 rounded text-3xl font-semibold bg-red-500 hover:bg-red-600">Stop</button>
<button className="px-4 py-2 rounded bg-orange-500 hover:bg-orange-600">Bonus</button>
<button className="px-4 py-2 rounded bg-green-500 hover:bg-green-600 border-2 border-green-300">Congratulations</button>
Note that:
- The additional classes are added to the existing classes.
- When extending an existing variant, the additional classes are added to the old ones.
if you are using TailwindCSS and want to avoid class conflicts then check the Merging TailwindCSS classes without conflict guide below.
if you don't like some default behavior of dye
and want to customize things, you can use the create_dye
function. This function takes a config object and returns a new dye
function that you can use like the default one.
The config has the following type:
type Config = {
mergeClasses: (classes: ClassValue[]) => string
}
type ClassValue = string | undefined | null | false
mergeClasses
the function to use to filter out falsy classnames and merge the rest. The default implementation just joins the classes with a space.
That's all it's on the config now, if you see other behaviors/flags to add, please let me know :)
Check a pratical example of using create_dye
in the Merging TailwindCSS classes without conflict guide below.
When using TailwindCSS, you probably want to avoid conflicts, simply concatenating classenames will not work. So follow the next steps:
- Add tailwind-merge to your project
npm install tailwind-merge
- Create a custom
dye
function that usestwMerge
fromtailwind-merge
:
import { create_dye } from 'react-dye'
import { twMerge } from 'tailwind-merge'
export const dye = create_dye({ mergeClasses: twMerge })
- Use the
dye
function exported above instead of the default one.
That's all.
Now you may have the following question: Why not just include tailwind-merge
as a dependency and use twMerge
by default?
I decided not to include it because:
- I would like this library to be generic, works with any collection of CSS classes. I don't want to tie it to TailwindCSS.
tailwind-merge
is great, but it would multiply the bundle size by 16! (8kb vs 0.5kb).
You can contribute to this library in many ways, including:
- Reporting bugs: Simply open an issue and describe the bug. Please include a code snippet to reproduce the bug, it really helps to solve the problem quickly.
- Suggesting new features: If you have a common use case that you think worth adding to the library, open an issue and we will discuss it. Do you already have an implementation for it? Great, make a pull request and I will review it.
Those are just examples, any issue or pull request is welcome :)
Check out the changelog for more information.