⚔️ Code Conqueror

🍃 A manageable CSS framework for React with TailwindCSS

May 26, 2020

A recent trend in React has been toward using component styling libraries like Styled Components, Emotion and Rebass. While these certainly have their advantages, they do have one major disadvantage — you need to name new components for the styled versions of html elements.

Tailwindcss separates concerns effectively between a css styling framework and javascript application code. Because of this you avoid components having too many props that affect their styling in favor of the vanilla classNames property controlling all the styles.

Why not use a css-in-js component framework?

<Box>
<Text>Hello World</Text>
</Box>;

This is very hard to decipher for a new developer to React. What are the underlying semantic components of those elements? Is

Text
a span or a paragraph tag? What were the props of box again? Does it have something for padding?

One also has to import all of these design components in every component in which they are used, a minor point, but an inconvenience nonetheless.

For a small component this may be tractable, but once you get to a larger component you end up with a soup of logic and styles mixed in a way that takes time jumping back and forth between files to decipher. Here's an example from Airbnb's codebase:

export default function RoomTypeFilter({ id, roomTypes, onUpdate }) {
return (
<div>
{ROOM_TYPES.map(
({
id: roomTypeId,
filterKey,
iconClass: IconClass,
title,
subtitle,
}) => {
const inputId = `${id}-${roomTypeId}-Checkbox`;
const titleId = `${id}-${roomTypeId}-title`;
const subtitleId = `${id}-${roomTypeId}-subtitle`;
const selected = roomTypes.includes(filterKey);
const checkbox = (
<Spacing top={0.5} right={1}>
<CheckBoxOnly
id={inputId}
onChange={() =>
onUpdate({ roomTypes: toggleArrayItem(roomTypes, filterKey) })
}
/>
</Spacing>
);
return (
<div key={roomTypeId}>
<ShowAt breakpoint="mediumAndAbove">
<Label htmlFor={inputId}>
<FlexBar
align="top"
before={checkbox}
after={<IconClass size={28} />}
>
<Spacing right={2}>
<div id={titleId}>
<Text light>{title}</Text>
</div>
<div id={subtitleId}>
<Text small light>
{subtitle}
</Text>
</div>
</Spacing>
</FlexBar>
</Label>
</ShowAt>
<HideAt breakpoint="mediumAndAbove">
<Spacing vertical={2}>
<CheckBox
id={roomTypeId}
onChange={() =>
onUpdate({
roomTypes: toggleArrayItem(roomTypes, filterKey),
})
}
subtitle={subtitle}
/>
</Spacing>
</HideAt>
</div>
);
}
)}
</div>
);
}

Here we can see while there are some semantic

div
elements for the most part everything is a component. Just to control the spacing around a checkbox we need a component and we need to know that it has the
top
and
right
properties (is that padding or margin?? you tell me). Also do do simply layout things like flexbox you require wrapping a lot of additional DOM nodes around things just to get the css correct (they have invented the
FlexBar
component). Also hiding and showing at breakpoints needs additional components to be done correctly.

As we'll see tailwind can short-circuit all fo these "design" components to boil your jsx down to logical blocs with

classNames
sprinkled in to handle the styles. That makes it much easier when scanning the code to figure out what is logical and what relates to styling.

Why TailwindCSS solves this problem

A much more straightforward way to build a design system in React is to use Tailwind instead of building out a set of building block components you build out a set of css utility classes. That way your markup is always just plain divs and spans (predictable) and its clear when there are things that affect reactivity vs just styling (all in className).

If you are not familiar with tailwind let me start with a simple example. Say we want to flex two divs with

justify-content: space-between
in tailwind you can do that simply by:

const SideBySide = () => (
<div className="flex justify-between">
<div>First</div>
<div>Second</div>
</div>
);

All we do is supply the

className
property to the div and apply some utility classes that are built into tailwind. It will take a bit of time to become accustomed to the utility class names (although most are intuitive and they are very customizable). But here we know for sure that we are rendering divs and that there is really no logic going on only styles being applied.

Let's take the example from Airbnb and refactor it a bit to use tailwind:

export default function RoomTypeFilter({ id, roomTypes, onUpdate }) {
return (
<div>
{ROOM_TYPES.map(
({
id: roomTypeId,
filterKey,
iconClass: IconClass,
title,
subtitle,
}) => {
const inputId = `${id}-${roomTypeId}-Checkbox`;
const titleId = `${id}-${roomTypeId}-title`;
const subtitleId = `${id}-${roomTypeId}-subtitle`;
const selected = roomTypes.includes(filterKey);
const checkbox = (
<CheckBoxOnly
className="pt-1 pr-2"
id={inputId}
onChange={() =>
onUpdate({ roomTypes: toggleArrayItem(roomTypes, filterKey) })
}
/>
);
return (
<div key={roomTypeId}>
<label className="invisible md:visible" htmlFor={inputId}>
<div className="flex justify-start pr-2">
<div id={titleId}>
<p className="text-gray-300">{title}</p>
</div>
<div id={subtitleId}>
<p className="text-sm text-gray-300">{subtitle}</p>
</div>
</div>
</label>
<CheckBox
className="visible md:invisible pt-2"
id={roomTypeId}
onChange={() =>
onUpdate({ roomTypes: toggleArrayItem(roomTypes, filterKey) })
}
subtitle={subtitle}
/>
</div>
);
}
)}
</div>
);
}

What we are left with is a bunch of semantic elements like

label
and
div
which have predictable behavior. Tailwind also exposes utility classes for dealing with showing and hiding elements at different breakpoints (which the airbnb code needs to invent new components for).

Speed of development with Tailwind

Another major factor in the Tailwind camp is the speed at which one can develop very good looking UIs without having to set up a bunch of boiler plate code or specific components for design. Often in React projects that use

styled-components
you will find directories of non-logical UI components that just apply styles to underlying semantic elements. Tailwind short circuits all of that. You don't need to mess with importing a bunch of components just for styling, it's clear which semantic elements underly the code etc.

Also the pre-built utility classes in Tailwind are a great starting point for building out a design system of your own. You basically just have to adjust the color / spacing values to fit with your brand if the default ones don't already and tailwind will do the work for you of generating the relevant utility classes.

Customizability concerns

Some complain that Tailwind "breaks css" or that it is hard to decipher the built in class names. But what they are missing is that tailwind is really just a way to make it easier to write vanilla css (after all thats what it compiles down to).

Tailwind styles cascade, meaning that if you apply a style to a parent element then its children inherit that style. This does mean one has to be wary about conflicting styles and which parent element is setting certain classes (some other css in js frameworks like styled-jsx try to avoid this by scoping everything to a specific element). But this is how CSS was intentionally designed, and with proper markup you gain extra efficiencies of letting the styles cascade -- eg setting font family at the root.

Tailwind also works more seamlessly with vanilla css files since it's really just vanilla css. So if you need a bit more flexibility like pseudo-elements you can naturally extend the

index.css
file that sets up tailwind or create your own css modules.