This is my first article in Dev.to, please be patient with the poor markdown-ing!
Alright, this article might or might not be for you.
If you're the type of person that finds some Tailwind UI components and copies all the classes without any second thought, this might be for you.
If you're passionate about Tailwind and you appreciate getting creative with its utilitarian classes, this might be for you.
If you struggle with Tailwind and wonder why can't we just stick to plain old CSS, this might be for you.
If you don't like repetition and suspect that (besides wasting your time with the previous statements) some of the things that I'll show here are something you're already aware of, this might still be for you β repetition can't be that bad and I really tried to bring in something for everybody!
The idea here is to go through a Tailwind speed-run and find things that could help you use it more efficiently. And if nothing resonates with you, share with me in the comment section what did I miss :D
(And yes, the assumption is that you use VSCode, and occasionally Next.js for a thing or two, although a lot of these topics remain valid also with other frameworks.)
Let's get started then, shall we?
Before you proceed reading, let's make sure you are already using Tailwind CSS Intellisense, because if you're not, you definitely should. It's impossible to remember all the classes that Tailwind offers, and a little while we type them out is really appreciated!
Speaking of classes...
Classes, classes everywhere!
Meme generated with Imgflip Meme Generator
Yes, let's make sure we start with the easy-peasy. Lots of people complain that Tailwind litters the project components with hecklots of classes, making it difficult to read through the component. To them I tell... have you heard of Tailwind Fold by Stivo? No? Now you have.
In his article about Hiding classes in VSCode, our friend and constant source of inspiration Flavio Copes goes through a quick look at how this VSCode extension simply shows and hides the classes through a simple click.
While this is a worthy approach, you might not want to click to toggle classes visibility (I know I don't), and therefore the next suggestion would be...
Tailwind + Sass (and a sprinkle of @mixin
if you will)
(We'll assume you're using Next.js, because, let's be honest, what else would you want to use if you had a choice?! I'm kidding, kinda :D)
We all love (I hope, otherwise why are you here?!) the power of Tailwind, but separating the functional part of a component from its styling is still a dream for many. The good news? You can achieve this by combining Tailwind with Sass! You just need to install Sass and everything will start working right off the bat!
Joaquin Collado, through Rootstrap, has an easy guide on how to Use Module SCSS with Tailwind in Next.js. Let's follow along!
First, install Sass
npm install --save sass
Then, create the .module.scss
for your components, e.g. Button.module.scss
.button {
@apply p-4 rounded bg-blue-500;
}
Import the styles in the component
import styles from './Button.module.scss';
And finally use them in your React component
/* ... */
<button className={styles.button}>Click Me</button>
/* ... */
Ta-da! π You will now be able, for most things, to separate the Tailwind classes from your component.
And you know what?! This approach allows you to use also the @mixin
properties if you're used to them!
Mixins allow you to define styles that can be re-used throughout your stylesheet. They make it easy to avoid using non-semantic classes like
β @mixin and @include explanation from the official Sass documentation.float-left
, and to distribute collections of styles in libraries.
Have a look at this demo repository in CodeSandbox where you can see Tailwind + Sass + a simple implementation of @mixin
in action, or β if you prefer β test this implementation of Tailwind + Sass + @mixin
locally by cloning it from GitHub.
So, let's take it slow and check... what do we have over there?
// page.tsx
import SassButton from "@/components/sassButton/sassButton";
/* ... */
<SassButton>Hello, I am a simple button</SassButton>
<SassButton className="block mt-4" variant="cancel">
Me too!
</SassButton>
/* ... */
<SassButton className="w-full m-2" variant="alert">
I am a variant that takes in
both color and optional text color
</SassButton>
We have a poorly-named SassButton
component that can accept two props (ignoring the children, in our case the text we want our button to have), className
and variant
. Both props are optional, and while we get later to className
and best practices on how to use that, let's focus on the variant
part.
Now, moving to the button component
// SassButton.tsx
import cx from "classnames";
import { FC } from "react";
import styles from "./sassButton.module.scss";
interface SassButtonProps {
variant?: "default" | "cancel" | "alert";
className?: string;
children: React.ReactElement | string;
}
const SassButton: FC<SassButtonProps> = ({
variant = "default",
className = "",
children,
}) => (
<button className={cx(
styles["button-" + variant],
className
)}>
{children}
</button>
);
export default SassButton;
we use the variant
prop to determine what style the button will inherit from our sassButton.module.scss
(which will be button-<variant_name>
), and when no variant is set we just set its value to default
.
Let's finally have a look at the Sass module.
@mixin button-styles(
$button-bg-color,
$button-text-color: "white"
) {
background-color: $button-bg-color;
color: $button-text-color;
@apply p-4 rounded;
}
.button-default {
// here you can pass the background color
// text color will use the default from the @mixin
@include button-styles(blue);
}
.button-alert {
// here you can pass both background color and text color
@include button-styles(orange, black);
}
.button-cancel {
@include button-styles(#ff0000);
}
Our sass module starts with the overall shape and appearance of our button through the @mixin
directive called button-styles
, and it uses $button-bg-color
and $button-text-color
as variables to customize the color of the background and the text of the button.
Subsequently, we reuse the same setup by providing the variants default alert and cancel with the desired background and text color (the latter being optional and defaulting to white if nothing else is specified) by calling on button-style
through the @include
directive.
(Congratulations, you just had an absolute speed-run on how to use @mixin
if this is your first time!)
Notice that with this approach nothing forbids us to use Tailwind classes at any point; we are already using simultaneously traditional CSS properties combined with Tailwind classes. Allegedly, no one could stop us from doing
.button-cancel {
@include button-styles(#ff0000);
@apply text-xs underline;
}
and it would be up to us to choose how to organise and create all the variants.
Heck, you might and think "why bringing @mixin
up at all?" and in general I would say that Tailwind on its own is more than enough, but in a system that needs to contemplate multiple variants of the same component, @mixin
could be the solution you were looking for β also, my objective with this article is to showcase more possibilities on how to work with Tailwind!
(Also... for some reason, I am not successful setting up a default value for $button-bg-color
, if you know why let me know in the comments how to set all the parameters optional!)
So do you remember that className
I said I was gonna mention again later? Now it's the time, for we are going to talk about...
The misunderstood art of managing space between elements and sections
If you're working with a good design system, I'd expect everything to be well-divided in sections and you being blessed with the consistency of everything spaced consistently throughout the project.
A good design system would have an atomic structure, where each smallest atom is a discernible component that may coincide to one or a few more HTML elements in the page that provide some value. Using these atoms/components together would form molecules, which would be the equivalent of a header, footer, sidebar β which all are sections of a website page β and a full page could be then considered an organism.
The reality is that most times, designers work in components and not in sections, meaning that there's no real concept of molecules, just atoms and straight into organisms. This implies that there is easily a lot of discrepancy on how much space there can be above an heading or below a section based on the rest of the content of the page if there is no clear demarcation of the end of a section and the beginning of another.
In the above image (titled Bad spacing), the elements have an add a margin down approach to space components between each other. While this might still result in the desired appearance for some pages, reusing the same components in new pages will cause issues as the margins might differ. There is no clarity on how much space the navigation or the article header should have, nor how much their components should have space around them.
In this other image (titled "Better spacing") we can see an improved understanding of spacing between elements and a clearer use of sections. We are able to deduct how much space there should be around each component, not only under, and which part of the overall space doesn't really belong to any component.
(Yes, I know, some components such as "Tags" should also be able to see the settings of each single "Tag" and in my picture they are all compact together, but I just wanted to get the spacing point across... :D)
While we are not here to discuss about design systems (although I would anytime), I want you to acknowledge that components shouldn't directly take care of accommodating spacing based to their position in a page, but rather, they should be able to circumstantially receive and accommodate their spacing directives.
What do I mean by circumstantially? Nothing more than accepting classes related to the spacing that is needed for the page where the component will show up.
You can check this CodeSandbox to test how to give arbitrary spacing classes to a component or, like before, you can check how to give arbitrary spacing classes from this GitHub repository.
Let's dig into the code.
We have three different pages, app/pages.tsx
, app/articles/page.tsx
and app/about/page.tsx
that make use of the same <HeaderTitle>
component. The homepage uses the component without worrying much about its spacing in the page:
// app/page.tsx
/* ... */
<HeaderTitle
title="Lorem ipsum"
description="This HeaderTitle component
doesn't have any extra spacing setting"
/>
/* ... */
Meanwhile, both app/articles/page.tsx
and app/about/page.tsx
infer an extra className
property that allows the <HeaderTitle>
component to take up different space in the page.
// app/articles/page.tsx
/* ... */
<HeaderTitle
title="Lorem ipsum"
description="This HeaderTitle has some extra margin
around it to fit better something like an article title"
className="my-24 mx-12" // we added margins for this page
/>
/* ... */
// app/about/page.tsx
/* ... */
<HeaderTitle
title="Lorem ipsum"
description={
<>
<ul>
<li>
This HeaderTitle shows how much
flexibility you can have
</li>
<li>
while the component itself doesn't have
to include natively any 'fixed'
position
</li>
</ul>
</>
}
// with this approach, we can also infer
// other styles related to the position of the element
className="fixed right-0 top-24"
/>
/* ... */
The point here is that the code of the <HeaderTitle>
component itself remains untouched and un-duplicated, while its spacing properties are relative to the contexts it gets used on, allowing for flexibility of usage across different pages and different needs.
You might want to argue now "couldn't we just wrap the component into another div whenever we need more space around it?", and while that is possible, it also creates extra elements that might create challenges while delivering semantic HTML. Also, it really helps us categorizing which classes we need for its spacing and positioning, and which ones are for the component itself.
Which, guess what, leads us straight into the next topic!
Grouping by purpose for the sake of readability
It takes absolutely nothing to make Tailwind classes difficult to digest; you check one component's styling and all you see is a long list of classes and no quick glance will prevent you from mistakenly apply a second mx-
class or so.
So, next in my personal recommended Tailwind best-practices, is to think about what each class is doing β layout, spacing, typography, colors, etc. β and group by that!
For example, instead of writing
<div class="mt-4 bg-blue-500 text-white p-6 rounded-lg shadow-lg hover:bg-blue-700">
<!-- content -->
</div>
You could organize them like this
<div class="p-6 mt-4 rounded-lg shadow-lg bg-blue-500 text-white hover:bg-blue-700">
<!-- content -->
</div>
Grouping similar classes together makes it easier to read and understand what stylings are being applied to the element.
If it sounds daunting as a task, worry not! Someone has already thought of a VSCode extension, Tailwind Raw Reorder, that will take care of the sorting for you! (And apparently, it also works in module.scss
files without any extra configuration!)
The order that the extension proposes is as follows (or at least ChatGPT think it is β at least I couldn't find this from anywhere else):
- Layout: container, box-border, box-content, etc.
- Positioning: static, fixed, absolute, relative, sticky, etc.
- Flex and Grid: flex, inline-flex, grid, inline-grid, etc.
- Spacing: m-0, p-0, space-x-0, etc.
- Sizing: w-full, h-full, max-w-full, min-h-full, etc.
- Typography: font-sans, text-sm, font-bold, etc.
- Background: bg-white, bg-opacity-50, etc.
- Border: border, border-0, rounded, ring, etc.
- Effects: shadow, opacity-0, etc.
- Transitions and Transforms: transition, duration-300, ease-in, scale-100, etc.
- Miscellaneous: cursor-pointer, select-none, etc.
You can read more about the development journey of Tailwind Raw Reorder on Reddit, where people talk also about the official recommendation for Automatic class sorting with Tailwind (which I don't recommend anymore because it only sorts classes alphabetically, although I wouldn't necessarily diss, as I did like the combo of Tailwind + Prettier it when I discovered its existence).
It is fair to note that Tailwind Raw Reorder by Andrew Trefethen is a fork of the now dated Headwind VSCode extension.
Well damn, if this isn't all that you need to make the most out of Tailwind in a smart way!
But wait! There's more!
Tailwind Merge to the rescue
While I've already written a small snippet about Tailwind Merge, let me reiterate here what's for.
Like the name suggests, tailwind-merge
npm package by Dany Castillo offers a solution that combines and reuses Tailwind utility classes.
Suppose you have a button component that can be either primary or secondary, with different styles for each state, some styles taken from a button.module.scss
and perhaps with something else inherited by className
as we have seen is possible from above (oh god, what a mess β I am legally obligated to mention the classic "Your scientists were so preoccupied with whether or not they could that they didn't stop to think if they should.").
Instead of having to figure out whyyy is the padding not working as you'd expect, you can use tailwind-merge and give hierarchy on how all the various classes take priority.
// button.tsx
import { FC, ReactNode } from 'react';
import { twMerge } from 'tailwind-merge';
import styles from './button.module.scss';
interface ButtonProps {
type?: 'primary' | 'secondary';
className?: string;
children: ReactNode;
}
const Button: FC<ButtonProps> = ({ type = 'primary', className, children }) => {
const baseClasses = 'p-4 rounded-lg';
const typeClasses = type === 'primary' ? 'bg-blue-500 text-white' : 'bg-gray-500 text-black';
return (
<button className={twMerge(baseClasses, typeClasses, styles.button, className)}>
{children}
</button>
);
};
export default Button;
Assuming a fictitious button.module.scss
containing
// button.module.scss
.button {
@apply text-xl;
}
The button will eventually have the following classes
'p-4 rounded-lg bg-red-500 underline text-xl text-white'
This approach eliminates potential conflicts and contradictions and hopefully keeps your elements rendered with fewer clean(er) classes.
The one caveat of Tailwind Merge, imho, is that you would have to start using it at the beginning of a project; however, nothing prevents you from progressively add it into the codebase.
Now, what else? Well, there's plenty!
Speed it up with component libraries
We all like to keep rebuilding the wheel, as every project proposes different nuances and we just want that button to work exactly as we had it envisioned. But the key to a successful "I build it my way" is to know when to build and when to borrow.
shadcn/ui offers a great set of components (styled with Tailwind) that you can take as-is and customize all while they already include some accessibility contemplations. It differs from other libraries as it invites you to be hands-on the actual component and it allows you to modify it to your exact needs without having to create extra levels of abstractions for customisation.
It is worth to note that, like everything, it is not perfect and sometimes you might want to rewrite a couple of things here and there. For example, its class handling proposes a mix of clsx
and twMerge
that might be redundant, as described by Pablo Haller in his article Something I donβt like from shadcn/ui.
That being said, shadcn/ui is a great way to speed up your work, especially combined with some good Figma prototyping made simple for you by Pietro Schirano with their Figma @shadcn/ui - Design System.
And while shadcn/ui is a great free solution to implement ready-made-tailwind-styled components, you might want to use a more mature system such as Flowbite.
Flowbite (whose main contributor is ZoltΓ‘n SzΕgyΓ©nyi) is a component library that is also built on top of Tailwind CSS. It provides a broader set of UI components that you can easily integrate into your Tailwind projects and it really helps you making everything look professional and curated from the very beginning.
On top of everything, Flowbite proposes a lot of videos that help you navigate the new ecosystem, as well as a lot of constant updates to their product, which we know is something we desire in a system that we want to implement and hopefully use for a long time.
If you want to familiarise with a great design system but you're not ready yet to make a monetary commitment, Flowbite is the right resource nonetheless. They offer also a free version of their Flowbite Design System, which can help you speed up your prototyping and design process while keeping high-quality mockups β and eventually figure out if Flowbite is the solution you were looking for.
Let's talk about forms
Got no time to make that form pretty? Worry not, because Tailwind Forms can come to the rescue to style in a consistent manner all your forms!
It provides base styles for form controls like inputs, text areas, checkboxes, and radio buttons.
You can simply install the plugin
npm install @tailwindcss/forms
add it to your tailwind.config.js
module.exports = {
//...
plugins: [
require('@tailwindcss/forms'),
],
}
and that's pretty much it! Wanna customise something more? Feel free to add some extra styles to the form elements like you normally would!
Be sure to check out the Tailwind Forms documentation and be sure to checkout the Tailwind Form example they provide!
Snippity snippets!
I'd like to end this article by shining some lights on other majestic work that people have done and shared.
The homepage of Tailwind UI.
You can find a ready-made collection of templates in Tailwind UI, developed and curated by the very same creators of Tailwind. It's a nice mature collection of components and templates, with the only downside that it will cost you some money. But hey, quality stuff nevertheless!
The homepage of Tailwind Snippets.
Tailwind Snippets, as it mentions in their homepage, proposes a collection of UI templates to speed up your UI development using React and Tailwind CSS. Quick and easy to browse, find the elements you need and copy away!
Tailwind Snippets website, but the one of the Tailwind Snippets VSCode plugin.
With the same name but with a VSCode extension coming along with it, Tailwind Snippets is a VSCode extension that allows you to take advantage of the numerous snippets available in Tailwind Snippets and developed by ZS Software Studio.
Tailwind Components website.
On the same note, you'll find Tailwind Components with its community-shared free-to-use components... all the options!
And these are just a few of the ready-made snippets you can copy away. Do you have a favourite one, or am I missing out on something good? Please share!
I hope you found some good resources in this article, and that ideally you might have learned a thing or two. If you have any questions or additional tips, drop a comment below!
Sources and inspiration
- Tailwind CSS Intellisense by Tailwind CSS
- Tailwind Fold by Stivo
- Hiding classes in VSCode by Flavio Copes
- How to Use Module SCSS with Tailwind in Next.js by Joaquin Collado via Rootstrap
-
ChatGPT prompt:
Can you show me what order would the class list (handled by Tailwind Raw Reorder) have?
- Tailwind Raw Reorder by Andrew Trefethen
-
tailwind-merge
npm package by Dany Castillo - shadcn/ui main website
- Something I donβt like from shadcn/ui by Pablo Haller
- Flowbite by ZoltΓ‘n SzΕgyΓ©nyi
- Flowbite Design System on Figma
- Tailwind Forms documentation
- Tailwind UI
- Tailwind Snippets
- Tailwind Snippets, the one that offers the Tailwind Snippets VSCode plugin
- Tailwind Components
- Cover: 3d young school student by Freepik, Flat abstract wireframe background by Freepik, Tailwind logotype from Tailwind Official Brand page, semi-random coding text generated with Carbon
Originally posted in oh-no.ooo (Level up your Tailwind game), my personal website.
Top comments (7)
Woow!! This article is really valuable. We use tailwind on a daily basis on our project, with Vue, Nuxt & Typescript as more stack.
Tailwind is so simple, yet effective.
They have done a great job developing it!
Antonio, CEO & Founder at Litlyx
Yes they did! I'm always amazed at how much can be done with it!
Thank you for the comment, I commend to you and your team for the amazing product you're building!
Thanks a lot!
Well done Lucia. You are indeed a good writer, I've seen your blog.
I do use this structure for large projects but for little one... Is it just me? Or I'm just too lazy to create a file for styles :(
Development tools are making us lazy and lazy, back then I configured Webpack to whole project structure. Now I can't even create a new file
Thank you so much for the kind words Tahazzot!
I don't see any harm in not wanting to dedicate a file to styles, it really depends on the project you're working on and if you're not feeling the pressure to separate the styles from the component I think it's perfectly okay to proceed with the classes inside the component. I personally have had a mix of both approaches!
(Tell you what, lazy is fine if it enables work to be done fast! :D)
One could also go as far as taking it as a challenge and try to add as little styling as possible! I'm hoping that next time I will do a small project I can put into use Tailwind prose class for example, instead of having to style my own Markdown!
Well Explain
Thank you so much, I really appreciate!