Introduction to Tailwind CSS
Published Mar 20, 2021
Table of Contents
- Introduction
- What is Tailwind CSS?
- The Art of Writing CSS
- Repetition
- The Tailwind CSS Approach
- Writing CSS Like Shakespeare
- Dealing With Remembering
- Tailwind Components
- Conclusion
Introduction
I love writing CSS. Itās a satisfying feeling when you get into the flow, and assemble the pieces of a site like a puzzle. I understand not everyone might feel that way ā and a lot of us instead reach for a popular CSS framework.
While each have their pros, and cons ā thereās always friction that gets in the way of writing styles like naming, and context switching between files regardless if youāre using a framework, or writing it yourself.
Wouldnāt it be nicer if writing styles could flow like prose?
What is Tailwind CSS?
Tailwind CSS is a utility-first CSS framework. In other words, instead of having premade components like other frameworks, Tailwind only offers utility classes to build your site. For example p-8
is the equivalent of padding: 2rem
.
Isnāt this more work? You might ask whatās the point, if you can use premade components with traditional CSS frameworks.
The problem is despite that, they barely offer utility beyond what makes up a component, so you still write your styles separately.
Modifying the base styles is often a bad developer experience. Remember when you swore youād never use !important
? Reality is often disappointing.
āUtility classes help you work within the constraints of a system instead of littering your stylesheets with arbitrary values. They make it easy to be consistent with color choices, spacing, typography, shadows, and everything else that makes up a well-engineered design system.ā
Writing styles with Tailwind is enjoyable because it removes that friction I mentioned at the start. It letās you prototype ideas quick since itās intuitive, and tied to your markup. Itās limiting, but extensible.
Enough talk, letās go through an example.
The Art of Writing CSS
Letās start with a simple product card using plain HTML, and CSS before we refactor to Tailwind.
Youāre welcome to recreate it yourself if you want to retain what you learn. You can use Codepen if you donāt feel like opening your editor.
This is our finished card, with the source code.
You can copy the HTML code, if you want to get practice refactoring it to Tailwind later.
<div class="container">
<article class="card">
<div class="card-image">
<img src="https://i.ibb.co/09nx6Jt/converse.webp" alt="Converse sneakers" />
</div>
<div class="card-details">
<h2 class="title">Converse Sneakers</h2>
<span class="description">High Top (Lemon Yellow)</span>
<span class="price">$60</span>
</div>
<div class="card-colors">
<span class="screen-reader-only">Colors available</span>
<button aria-label="Yellow" class="color-btn">
<div class="color-container">
<div class="color-first yellow"></div>
<div class="color-second white"></div>
</div>
</button>
<button aria-label="Red" class="color-btn">
<div class="color-container">
<div class="color-first red"></div>
<div class="color-second white"></div>
</div>
</button>
<button aria-label="Blue" class="color-btn">
<div class="color-container">
<div class="color-first blue"></div>
<div class="color-second white"></div>
</div>
</button>
<button aria-label="Black" class="color-btn">
<div class="color-container">
<div class="color-first black"></div>
<div class="color-second white"></div>
</div>
</button>
</div>
<div class="card-add">
<button class="add-btn">
<p class="add-text">Add to Cart</p>
<svg class="plus-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</button>
</div>
</article>
</div>
@import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600;700&display=swap");
/* Reset */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Variables */
:root {
--app-bg-color: hsl(220, 13%, 91%);
--card-bg-color: hsl(0, 0%, 100%);
--text-color: hsl(215, 28%, 17%);
--border: 1px solid hsl(220, 13%, 91%);
}
/* Container */
.container {
height: 100vh;
display: grid;
place-content: center;
padding: 1rem;
font-family: "Montserrat", sans-serif;
background-color: var(--app-bg-color);
color: var(--text-color);
}
/* Card */
.card {
max-width: 24rem;
background-color: var(--card-bg-color);
border-radius: 0.25rem;
box-shadow: 0 10px 15px -3px hsla(0, 0%, 0%, 10%),
0 4px 6px -2px hsla(0, 0%, 0%, 5%);
overflow: hidden; /* for the image covering our border */
}
.card-image img {
display: block;
height: 100%;
width: 100%;
object-fit: cover;
}
.card-details {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-top: 1rem;
padding: 0 1rem;
}
.title {
font-size: 1.125rem;
font-weight: 600;
}
.description {
font-weight: 400;
}
.price {
font-weight: 600;
}
.card-colors {
display: flex;
gap: 1rem;
margin-top: 1rem;
padding: 0 1rem;
}
.color-btn {
padding: 0.125rem;
background: none;
border: var(--border);
border-radius: 50%;
cursor: pointer;
}
.color-container {
height: 1.5rem;
width: 1.5rem;
transform: rotate(-45deg);
border-radius: inherit;
overflow: hidden;
}
.color-first {
height: 50%;
}
.color-second {
height: 50%;
}
.card-add {
margin-top: 1rem;
padding: 1rem;
border-top: var(--border);
}
.add-btn {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
font-family: inherit;
font-weight: 700;
color: inherit;
background: none;
border: none;
cursor: pointer;
}
.add-btn:hover {
text-decoration: underline;
}
.add-text {
font-size: 1rem;
}
.plus-icon {
height: 1.5rem;
width: 1.5rem;
}
/* Utils */
.screen-reader-only {
position: absolute;
width: 1px;
height: 1px;
clip: rect(0, 0, 0, 0);
overflow: hidden;
}
.yellow {
background-color: hsl(38, 92%, 50%);
}
.red {
background-color: hsl(0, 84%, 60%);
}
.blue {
background-color: hsl(217, 91%, 60%);
}
.black {
background-color: hsl(215, 28%, 17%);
}
.white {
background-color: hsl(0, 0%, 100%);
}
/* Media */
@media (prefers-color-scheme: dark) {
:root {
--app-bg-color: hsl(215, 28%, 17%);
--card-bg-color: hsl(217, 19%, 27%);
--text-color: hsl(0, 0%, 100%);
--border: 1px solid hsl(220, 9%, 46%);
}
}
@media (min-width: 640px) {
.container {
padding: 0;
}
}
Thereās not much to the styles. Weāre using Google Fonts, CSS variables to control dark mode, and I got the icon from Heroicons.
Iāve added some basic media queries such as dark mode support, and responsive to show how it translates to Tailwind.
If youāre wondering why for the color options we donāt use a simple gradient ā itās because of an issue with jagged edges when a color abruptly stops in a gradient causing banding.
.gradient {
background: linear-gradient(145deg, black 50%, white 50%);
}
Itās a fixable problem, but I opted for this solution instead since it varies across browsers.
Repetition
I mentioned earlier how I enjoy writing CSS, but it doesnāt mean I enjoy repetition. How many times do we write out the same things over again?
I mean the tedious nature of curly brackets, semicolons, and coming up with class names, which leads to using metodologies like BEM, but weāre only fooling ourselves believing this approach is truly a separation of concerns when our markup becomes dependant on the class names we assign it.
I love SASS, but truth be told, I rarely find use for it nowadays with CSS variables being a thing, and how everything is componetized if youāre using a JavaScript framework, or having the option of using CSS-in-JS in popular frameworks such as React.
The Tailwind CSS Approach
To get up and going, weāre going to use Tailwind Play which is an official playground provided by the Tailwind team. It has everything set up. Once you open it, clear the example in the HTML file, so we can work from a blank slate.
If you want to use Tailwind in your project you can read the installation steps from the documentation.
Let me briefly explain how Tailwind works.
Tailwind uses a tailwind.config.js
config file that uses PostCSS under the hood that uses the @tailwind
directive inside your CSS file at build-time to inject:
- base (Tailwind base styles, CSS reset)
- components (component classes)
- utilities (utiliy classes)
Donāt panic! This is just an explanation of how it works. To set it up is straightforward, and doesnāt require any configuration.
The amount of utility classes can lead to huge files, but Tailwind purges unused styles for you creating a tiny file in production. You can learn more about it if you read optimizing for production.
Some people shrug their shoulders at resets, but when youāre recreating a design. The amount of CSS you write is negligible since you only end up having to add a couple of lines of styles for things such as headings, and paragraphs.
Youāre going to rewrite the defaults anyhow, if you want consistency, and not spending a lot of time inspecting what styles youāre trying to change.
Tailwind among other things also has support for dark mode, media queries, and plugins like aspect ratio that gives elements a fixed aspect ratio making it easy to use those features without thinking about implementation details (at the time of writing this only Chrome, and Edge support the built-in aspect-ratio).
Letās refactor the earlier example to use Tailwind.
Writing CSS Like Shakespeare
In the CSS section of Tailwind Play, we can import our font. Donāt think itās anything magical. Itās a normal CSS file.
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600;700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
We can declare the font here like you would. Instead, letās modify the config instead. Tailwind by default doesnāt come with everything enabled due to file size concerns (Tailwind Play has everything enabled).
That means we can also specify the base Tailwind colors we want to use. We can look at available Tailwind colors for example, but we can also add our own under config.
const colors = require('tailwindcss/colors')
module.exports = {
darkMode: 'media',
theme: {
color: {
white: colors.white,
gray: colors.gray,
yellow: colors.yellow,
red: colors.red,
blue: colors.blue,
black: colors.black,
},
fontFamily: {
serif: ['Montserrat', 'system-ui'],
},
},
variants: {},
plugins: [],
}
You can copy the original HTML code if you wish to follow along. The nice thing is that we donāt have to write any resets. Weāre going to start by replacing the classes with Tailwind.
Letās start with the container.
<div class="grid h-screen p-4 font-serif text-gray-800 bg-gray-200 place-content-center dark:bg-gray-800 dark:text-gray-50 sm:p-0">
<!-- ... -->
</div>
Notice the helpful autocomplete feature when you type out Tailwind classes. This makes for a nicer developer experience. If youāre using VS Code I recommend you get Tailwind CSS IntelliSense. If at any point you lose the suggestions, press Ctrl + Spacebar.
The first thought you might have is āwhoa, thatās a lot of markup!ā. Once you shift how you think, youāre not going to want to go back. I believe itās wrong saying that using Tailwind is not having to write CSS. Itās the opposite. You write CSS, thatās more terse.
Youāre able to change things as you think, not having to switch to a separate file, and think about naming conventions. It letās you be more creative, and productive.
Letās do the same for the card.
<div class="grid h-screen p-4 font-serif text-gray-800 bg-gray-200 place-content-center dark:bg-gray-800 dark:text-gray-50 sm:p-0">
<article class="max-w-sm overflow-hidden bg-white rounded shadow-lg dark:bg-gray-700">
<!-- ... -->
</article>
</div>
Notice how simple adding dark mode is with the dark
variant, and the use of media queries. Tailwind uses a mobile first approach with the default breakpoints being sm
, md
, lg
, xl
, 2xl
.
We can even remove most of the styles from the image, because of Tailwind defaults.
<div class="grid h-screen p-4 font-serif text-gray-800 bg-gray-200 place-content-center dark:bg-gray-800 dark:text-gray-50 sm:p-0">
<article class="max-w-sm overflow-hidden bg-white rounded shadow-lg dark:bg-gray-700">
<div>
<img class="object-cover" src="https://i.ibb.co/09nx6Jt/converse.webp" alt="Converse sneakers"/>
</div>
<!-- ... -->
</article>
</div>
Letās style the details such as title, description, price.
<div class="grid h-screen p-4 font-serif text-gray-800 bg-gray-200 place-content-center dark:bg-gray-800 dark:text-gray-50 sm:p-0">
<article class="max-w-sm overflow-hidden bg-white rounded shadow-lg dark:bg-gray-700">
<div>
<img class="object-cover" src="https://i.ibb.co/09nx6Jt/converse.webp" alt="Converse sneakers"/>
</div>
<div class="flex flex-col gap-0.5 mt-4 px-4">
<h2 class="text-lg font-semibold">Converse Sneakers</h2>
<span class="font-normal">High Top (Lemon Yellow)</span>
<span class="font-semibold">$60</span>
</div>
<!-- ... -->
</article>
</div>
Next up is the color section.
<div class="grid h-screen p-4 font-serif text-gray-800 bg-gray-200 place-content-center dark:bg-gray-800 dark:text-gray-50 sm:p-0">
<article class="max-w-sm overflow-hidden bg-white rounded shadow-lg dark:bg-gray-700">
<div>
<img class="object-cover" src="https://i.ibb.co/09nx6Jt/converse.webp" alt="Converse sneakers"/>
</div>
<div class="flex flex-col gap-0.5 mt-4 px-4">
<h2 class="text-lg font-semibold">Converse Sneakers</h2>
<span class="font-normal">High Top (Lemon Yellow)</span>
<span class="font-semibold">$60</span>
</div>
<div class="flex gap-4 px-4 mt-4">
<span class="sr-only">Colors available</span>
<button aria-label="Yellow" class="p-0.5 bg-white border border-gray-200 dark:border-gray-500 rounded-full cursor-pointer">
<div class="w-6 h-6 overflow-hidden transform -rotate-45 rounded-full">
<div class="bg-yellow-500 h-1/2"></div>
<div class="h-1/2 white"></div>
</div>
</button>
<button aria-label="Red" class="p-0.5 bg-white border border-gray-200 dark:border-gray-500 rounded-full cursor-pointer">
<div class="w-6 h-6 overflow-hidden transform -rotate-45 rounded-full">
<div class="bg-red-500 h-1/2"></div>
<div class="h-1/2 white"></div>
</div>
</button>
<button aria-label="Blue" class="p-0.5 bg-white border border-gray-200 dark:border-gray-500 rounded-full cursor-pointer">
<div class="w-6 h-6 overflow-hidden transform -rotate-45 rounded-full">
<div class="bg-blue-500 h-1/2"></div>
<div class="h-1/2 white"></div>
</div>
</button>
<button aria-label="Black" class="p-0.5 bg-white border border-gray-200 dark:border-gray-500 rounded-full cursor-pointer">
<div class="w-6 h-6 overflow-hidden transform -rotate-45 rounded-full">
<div class="bg-gray-800 h-1/2"></div>
<div class="h-1/2 white"></div>
</div>
</button>
</div>
<!-- ... -->
</article>
</div>
āThatās great, but itās a lot of repetitionā you might be thinking. While you would be right, keep in mind when using a framework you often loop through some data you get. You would only have to change it in one place ā if it was a component.
Thereās other ways of extracting out classes (see the documentation), but itās not recommended as you get into the same predicament as writing regular styles. One helpful feature is to toggle word wrap in your editor.
The last thing left is the āAdd to Cartā section.
<div class="grid h-screen p-4 font-serif text-gray-800 bg-gray-200 place-content-center dark:bg-gray-800 dark:text-gray-50 sm:p-0">
<article class="max-w-sm overflow-hidden bg-white rounded shadow-lg dark:bg-gray-700">
<div>
<img class="object-cover" src="https://i.ibb.co/09nx6Jt/converse.webp" alt="Converse sneakers"/>
</div>
<div class="flex flex-col gap-0.5 mt-4 px-4">
<h2 class="text-lg font-semibold">Converse Sneakers</h2>
<span class="font-normal">High Top (Lemon Yellow)</span>
<span class="font-semibold">$60</span>
</div>
<div class="flex gap-4 px-4 mt-4">
<span class="sr-only">Colors available</span>
<button aria-label="Yellow" class="p-0.5 bg-white border border-gray-200 dark:border-gray-500 rounded-full cursor-pointer">
<div class="w-6 h-6 overflow-hidden transform -rotate-45 rounded-full">
<div class="bg-yellow-500 h-1/2"></div>
<div class="h-1/2 white"></div>
</div>
</button>
<button aria-label="Red" class="p-0.5 bg-white border border-gray-200 dark:border-gray-500 rounded-full cursor-pointer">
<div class="w-6 h-6 overflow-hidden transform -rotate-45 rounded-full">
<div class="bg-red-500 h-1/2"></div>
<div class="h-1/2 white"></div>
</div>
</button>
<button aria-label="Blue" class="p-0.5 bg-white border border-gray-200 dark:border-gray-500 rounded-full cursor-pointer">
<div class="w-6 h-6 overflow-hidden transform -rotate-45 rounded-full">
<div class="bg-blue-500 h-1/2"></div>
<div class="h-1/2 white"></div>
</div>
</button>
<button aria-label="Black" class="p-0.5 bg-white border border-gray-200 dark:border-gray-500 rounded-full cursor-pointer">
<div class="w-6 h-6 overflow-hidden transform -rotate-45 rounded-full">
<div class="bg-gray-800 h-1/2"></div>
<div class="h-1/2 white"></div>
</div>
</button>
</div>
<div class="p-4 mt-4 border-t border-gray-200 dark:border-gray-600">
<button class="flex items-center justify-between w-full font-bold cursor-pointer hover:underline">
<p class="text-base">Add to Cart</p>
<svg
class="w-6 h-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/>
</svg>
</button>
</div>
</article>
</div>
Thereās more you can do with hover, focus, and other states. You can look at the complete example on Tailwind Play.
Dealing With Remembering
Often people are overwhelmed with just the amount of regular CSS properties to remember. Tailwind requires you to understand CSS, unlike other frameworks which is a great way to learn.
You can have an easier time if you read the documentation, but I also recommend you dump the entire config file so you can reference it. This is done easily from the configuration step.
npx tailwindcss init tailwindcss-full-config.js --full
The real tailwindcss.config.js
config is going to be used for your project, and you can use this one for reference.
Tailwind Components
If you end up loving Tailwind, but still think itās a drag to create components from scratch thereās free resources like Tailwind Components.
If you want access to official Tailwind components, you can look at the paid components from Tailwind UI if you want to support the creators of Tailwind CSS.
Conclusion
Hope you give Tailwind a chance! You can use it for other things such as rapid prototypes. It might not be your cup of tea, and thatās fine. I just hope you at least give it a try, and keep an open mind.
If you want to learn Tailwind CSS, I highly recommend watching Tailwind CSS: From Zero to Production from the official Tailwind Labs YouTube channel. Youāre going to end up learning more about CSS in the process.