I'm merging css-in-js and Tailwind. Let me explain.
Why merge Tailwind with CSS-in-JS when twin.macro exists?
The first reason is reusability. Creating a button component which can vary via props requires manually mapping props to tailwind classnames. This gets very tiring very fast and makes it quite difficult to create React UI libraries with pure Tailwind.
The second reason is to improve my Typescript knowledge. It's not easy creating Typescript libraries, making them infer props, configurations all under the hood is even harder. And thus, my journey creating the library began.
The requirements
I made a list of requirements for this library, these are specific to challenges I faced developing UI libraries and React components with Tailwind.
- It must be able to merge multiple classnames
- It must have support for toggling different classnames based on input
- It must be able to create JSX like
button
orspan
- First class TypeScript support
Inspiration
When trying to come up with a good API I only had to look in one direction. Stitches.js. In my opinion, this is the best CSS in JS library made to date, and as such, it had the exact API I was looking to create. Please check their library out and give Modulz/WorkOS the credit they deserve!
Introducing @tw-classed/react
& @tw-classed/core
public alpha
These are both in alpha
stages as I'm still ironing out the Typescript types required to make them work. The types in core
are not stable enough for advanced usage just yet.
@tw-classed/react
is quite stable even for advanced Typescript usage, though I haven't performed any tsc performance tests nor heavy testing just yet. As such I've decided to only release these two libraries in a public alpha while I'm testing.
The full documentation is here. Below is a tiny introduction to the libraries.
@tw-classed/core
This is the framework agnostic core of the react library. It can be used standalone in vanilla JS to create dynamic Tailwind classes (or in other libraries when not using react). It simply acts as a class merger and toggler and can be used in any way imaginable.
import classed from "@tw-classed/core";
const simpleButton = classed(
"flex items-center py-3",
"hover:bg-gray-200 focus:bg-gray-200" // Accepts any number of arguments
);
console.log(simpleButton()); // => "flex items-center py-3 hover:bg-gray-200 focus:bg-gray-200"
const button = classed(
"flex items-center", // Apply your base styles
{
// Define your variants
variants: {
size: {
sm: "py-2 px-4",
md: "py-3 px-6",
lg: "py-4 px-8",
},
color: {
primary: "bg-blue-500 text-white",
secondary: "bg-gray-500 text-white",
},
},
defaultVariants: {
size: "md",
color: "primary",
},
}
);
console.log(button()); // => "flex items-center py-3 px-6 bg-blue-500 text-white"
console.log(button({ size: "sm" })); // => "flex items-center py-2 px-4 bg-blue-500 text-white"
console.log(button({ size: "lg", color: "secondary" })); // => "flex items-center py-4 px-8 bg-gray-500 text-white"
@tw-classed/react
This is the React library. It's a wrapper around @tw-classed/core
provides a React component creator similar to styled-components
or stitches.js
.
import classed from "@tw-classed/react";
const Button = classed("button", "flex items-center", {
variants: {
size: {
sm: "py-2 px-4",
md: "py-3 px-6",
lg: "py-4 px-8",
},
color: {
primary: "bg-blue-500 text-white",
secondary: "bg-gray-500 text-white",
},
},
defaultVariants: {
size: "md",
color: "primary",
},
});
const App = () => {
return (
<div>
<Button>Default</Button>
<Button size="sm">Small</Button>
<Button size="lg" color="secondary">
Large Secondary
</Button>
</div>
);
};
Down the road
There is still lots of things to do with this library before I feel confident in releasing a V1 stable release. Please don't use it in production just yet. Here is a list of future things.
- Class merger/differ (Omits classNames when a variant overrides)
- compoundVariants - Stitches inspired API to toggle variants when other variants are defined
- Typescript performance testing
- Stable
core
typescript API - currently onlyreact
is stable - Improve TailwindCSS Extension class RegEx for intellisense
Footnote
Thank you for reading this article! I hope many will test the library and provide feedback as its essential to improving. Any PR's and issues are welcome!
Here is the GitHub
I can be reached at My Website or through Twitter
Top comments (4)
Hey there, looks like some great work went into this project.
I like stitches too, particularly they way they do their variant styling.
Also thought I'd let you know there's been some updates and the SWC compiler is now usable alongside Twin with this config - so the first sentence in your intro is out of date 👍
Cheers! Updated :)
Looks cool!
Just wondering - I am right there is no a lot of runtime overhead like in classic css-in-js and it's just a class merging?
Hey! There is very little runtime overhead, you're right. All variants and classed are pregrouped ahead of rendering in the most performant way I could manage. Then once rendering the component will compute the correct classNames and thats it.
Additionally the loop is not very taxing. You usually dont have hundreds of props, thus the loop generating the class names is only looping over at most 100 props, most likely just 2 or 3.