A taste of Tailwind
Get acquainted with Tailwind's utility-first approach by building a custom message component.
21 lessons · 2:29:34
- 1A taste of Tailwind9:08
- 2Scaffolding a multipanel layout7:47
- Making nested panels scrollable4:59
- Customizing colors3:32
- Adding custom fonts6:44
- Building a server selector with SVG5:49
- Transitioning rounded corners4:29
- Styling active links5:08
- Building the active indicator11:23
- Eliminating duplication with components10:05
- Building the server header7:17
- Styling the channel list7:17
- Dynamic categories and channels12:35
- Styling the active channel9:01
- Using a state variable to style unread channels7:28
- Animated collapsible categories4:55
- Controlling layout with truncated text8:25
- Using the forms plugin to style the search box6:01
- Styling dynamic messages with components7:03
- Dynamic server and channel links3:21
- Hiding desktop navigation on mobile screens7:07
Video resources
Intro
To recreate Discord's message component with Tailwind CSS, we'll start with some basic markup:
export default function Home() {
return <p>Hello, Tailwind!</p>;
}
Since Discord has a darker theme, let's make the background of our message black using the bg-black
utility class:
export default function Home() {
return <p className="bg-black">Hello, Tailwind!</p>;
}
This utility class does just one thing: it sets the background-color
CSS property to black
. Next let's use the text-white
utility to make our text white:
export default function Home() {
return <p className="bg-black text-white">Hello, Tailwind!</p>;
}
This is really the heart of the Tailwind workflow. We apply these predefined utility classes directly to our markup to style our app without having to write any of our own custom CSS.
Next, let's make the min-height
of the element take up the full screen using the min-h-screen
utility class:
export default function Home() {
return (
<p className="min-h-screen bg-black text-white">Hello, Tailwind!</p>
);
}
We'll update this tag to be a div
, and give it a display of flex
with items-center
and justify-center
:
export default function Home() {
return (
<div className="flex min-h-screen items-center justify-center bg-black text-white">
Hello, Tailwind!
</div>
);
}
Tailwind utilities are composable, which just means that each one of these classes does just one thing. If you open the DevTools, you can see that the flex
class applies the display: flex
CSS rule, items-center
applies align-items: center
, and justify-center
applies justify-content: center
.
Using the DevTools is a great way to see what each utility class does as you learn Tailwind.
Colors
Discord's background is not pure black – its a shade of gray. So let's update our text's background color to get a closer match.
Tailwind ships with many colors, in 10 shades each. Colors use a 100-based numbering scale where 900 is the darkest (e.g. gray-900
) and 100 is the lightest (e.g. gray-100
), with 50
added more recently for even a touch lighter than 100 (e.g. gray-50
). You can view all the default colors in the documentation.
Note: Tailwind v3.3 added an extra
950
darker shade to each color, so in modern versions of Tailwind colors likegray-950
andblue-950
are now the darkest shades available.
Let's update our background to a dark gray with the bg-gray-700
class:
export default function Home() {
return (
<div className="flex min-h-screen items-center justify-center bg-gray-700 text-white">
Hello, Tailwind!
</div>
);
}
This gray doesn't match Discord's background exactly, but Tailwind's colors are fully customizable. In a later lesson we'll discuss customizing our colors to get a perfect fit, but for now we'll stick with the defaults.
Working with the spacing scale
Let's add some more markup to our message component. We'll start with the image:
<div className="flex min-h-screen items-center justify-center bg-gray-700 text-white">
<img src="/adamwathan.jpeg" alt="" />
</div>
By default the image renders at its large intrinsic size. To control the size, we can use Tailwind's width and height utilities:
<img className="h-4 w-4" src="/adamwathan.jpeg" alt="" />
h-4
and w-4
set the height and width of the image to 16 pixels. The 4 comes from Tailwind's spacing scale, which is in rems. Rems are a best practice to use for most units in CSS, but for the purposes of design you can translate the spacing units to pixels by multiplying by 4 (or dividing a pixel value by 4 to get the Tailwind spacing equivalent). So, w-4 h-4
ends up being 16px by 16px.
Note: Tailwind v3.4 added size utilities as a shorthand for setting the width and height together, so our
w-4 h-4
can be rewritten assize-4
.
If we look at Discord and inspect the image, we'll see it's 40px by 40px. 40 divided by 4 is 10, so if we use w-10 h-10
we'll end up with a 40px by 40px image:
<img className="h-10 w-10" src="/adamwathan.jpeg" alt="" />
Finishing the header
The avatar in Discord is rounded, so let's give our image a border radius using the rounded
utility:
<img className="h-10 w-10 rounded" src="/adamwathan.jpeg" alt="" />
The default rounded
class adds a 4px radius, but there's a rounded-full
class that makes it a full circle:
<img className="h-10 w-10 rounded-full" src="/adamwathan.jpeg" alt="" />
Now our image looks great!
Let's add some more markup for the name and date:
<img className="h-10 w-10 rounded-full" src="/adamwathan.jpeg" alt="" />
<p>
<span>adamwathan</span>
<span>01/15/2021</span>
</p>
We'll give the name a color of text-green-500
. We also want to add a bit of space to the right of the image, so let's add mr-4
to the image, and we'll also add mr-2
to the name to set aside the date:
<img
className="mr-4 h-10 w-10 rounded-full"
src="/adamwathan.jpeg"
alt=""
/>
<p>
<span className="mr-2 text-green-500">adamwathan</span>
<span>01/15/2021</span>
</p>
We can make the font-size of the name smaller with text-sm
and the date even smaller with text-xs
. We'll also lighten the date's color with text-gray-500
:
<img
className="mr-4 h-10 w-10 rounded-full"
src="/adamwathan.jpeg"
alt=""
/>
<p>
<span className="mr-2 text-sm text-green-500">adamwathan</span>
<span className="text-xs text-gray-500">01/15/2021</span>
</p>
Now let's wrap the image and the text in a new div
, make it a flex container, and make the paragraph flex items-baseline
so that everything lines up:
<div className="flex">
<img
className="mr-4 h-10 w-10 rounded-full"
src="/adamwathan.jpeg"
alt=""
/>
<p className="flex items-baseline">
<span className="mr-2 text-sm text-green-500">adamwathan</span>
<span className="text-xs text-gray-500">01/15/2021</span>
</p>
</div>
One thing you'll notice when working with Tailwind is that it doesn't abstract away CSS at all. So, if you want to lay out your UI elements using Flexbox, you have to know how Flexbox works in order to use it with Tailwind.
This is a good thing – the more you use Tailwind, the better at CSS, you'll actually become!
To wrap up our heading, let's make the name a little bit thicker by changing the font-weight using font-medium
:
<p className="flex items-baseline">
<span className="mr-2 text-sm font-medium text-green-500">
adamwathan
</span>
<span className="text-xs text-gray-500">01/15/2021</span>
</p>
The header is looking great.
Styling the message text
Let's grab the text of this first message and add it right below our heading:
<p className="flex items-baseline">
{/* ... */}
</p>
<p>
You should never use something like leading relaxed with a big font size, it goes against all typography best practices. Line height should decrease as font size gets bigger
</p>
We want these two elements to be on top of each other, so let's wrap this in a div
with flex
, and let's also wrap all of our content in a new div
with the max-w-large
utility to constrain the maximum width:
<div class="max-w-lg">
<div className="flex">
<img />
<div>{/* ... */}</div>
</div>
</div>
If we look at Discord, we can see that the text is not pure white – it's a bit softer than that. Let's make our paragraph text text-gray-300
:
<p className="text-gray-300">
You should never use something like leading relaxed with a big font size,
it goes against all typography best practices. Line height should
decrease as font size gets bigger
</p>
Let's move on to the next message. In Discord, you can see that when we hover each message, there is a hover treatment that highlights the full width of all the elements in each row. So conceptually, each message is in a separate wrapper.
So, let's put the second message in an entirely new container that is a sibling to this first image and paragraph wrapper:
<div class="max-w-lg">
{/* Message 1, with avatar and name */}
<div className="flex">
<img />
<div>{/* ... */}</div>
</div>
{/* Message 2 */}
<div>
<p>
You can override it in your config if you want but ultimately we
chose the defaults they did because they let you get results closest
to what a professional designer would do more easily
</p>
</div>
</div>
Since the second message doesn't have the avatar and name, it's not currently aligned with the text in the first message.
The great thing about using Tailwind's spacing scale for things like width and margins is that we can just add the numbers up to get a perfect alignment. Since the image is width 10, and it has a right margin of 4, the total space of the left gutter should be 14.
So let's add 14 units of left padding to the second message:
<div class="max-w-lg">
<div className="flex">
<img />
<div>{/* ... */}</div>
</div>
<div>
<p className="pl-14 text-gray-300">
You can override it in your config if you want but ultimately we
chose the defaults they did because they let you get results closest
to what a professional designer would do more easily
</p>
</div>
</div>
And it perfectly lines up. We also added text-gray-300
to match the text color.
Let's add some space between the messages by adding a wrapping div
with a bit of top margin:
<div class="max-w-lg">
<div className="flex">
<img />
<div>{/* ... */}</div>
</div>
<div className="mt-1">
<p className="pl-14 text-gray-300">
You can override it in your config if you want but ultimately we
chose the defaults they did because they let you get results closest
to what a professional designer would do more easily
</p>
</div>
</div>
And we'll grab the last message, and apply the same treatment:
<div class="max-w-lg">
<div className="flex">
<img />
<div>{/* ... */}</div>
</div>
<div className="mt-1">
<p className="pl-14 text-gray-300">
You can override it in your config if you want but ultimately we
chose the defaults they did because they let you get results closest
to what a professional designer would do more easily
</p>
</div>
<div className="mt-1">
<p className="pl-14 text-gray-300">
Since we changed this in tailwind 2 I’ve almost never used a leading
class at all
</p>
</div>
</div>
The messages are looking great!
Applying the hover treatment
Discord applies a hover treatment to each message that darkens the background. In Tailwind, we can apply any utility to a hovered state using the hover:
prefix.
Since our app's background is gray-700
, let's give each message a background of gray-800
on hover:
<div class="max-w-lg">
<div className="flex hover:bg-gray-800">{/* ... */}</div>
<div className="mt-1">{/* ... */}</div>
<div className="mt-1">{/* ... */}</div>
</div>
Now the message's background darkens on hover.
gray-800
is a little dark though, and Discord actually uses a background opacity on their treatment to lighten it up. Tailwind also has background-opacity
utilities that we can use to lighten the color up. Let's use 30% on hover:
<div class="flex hover:bg-gray-800 hover:bg-opacity-30">{/* ... */}</div>
Looks good!
There's also a shorthand we can use instead of adding the separate bg-opacity
class – we can use a slash (/
) after any color to apply opacity to it:
<div class="flex hover:bg-gray-800/30">{/* ... */}</div>
Finishing up
In Discord, there's some space around each message. Let's come back to our container and add some horizontal and vertical padding.
Tailind has px
and py
utilities as shorthands for adding left/right padding and top/bottom padding, respectively:
<div class="flex px-4 py-1 hover:bg-gray-800 hover:bg-opacity-30">
{/* ... */}
</div>
Now let's apply the same treatment to each message container:
<div class="max-w-lg hover:bg-gray-800">
<div className="flex px-4 py-1 hover:bg-gray-800 hover:bg-opacity-30">
{/* ... */}
</div>
<div className="mt-1 px-4 py-1 hover:bg-gray-800 hover:bg-opacity-30">
{/* ... */}
</div>
<div className="mt-1 px-4 py-1 hover:bg-gray-800 hover:bg-opacity-30">
{/* ... */}
</div>
</div>
And now each message has the spacing and hover treatment. Looking good!
We ended up with a high-fidelity reproduction of some custom UI here, and we didn't write a single line of CSS – no new files to maintain, and no custom naming pattern that we had to come up with or explain to our coworkers.
This is one of the biggest value propositions of Tailwind. We get to use all these predefined utilities directly in our markup, without having to make any new abstractions.
This is absolutely one of my favorite things about this library because how you break up your HTML and your templates depends so much on your team and what technology you're using. With Tailwind, all your styling code lives right here in the markup, and just comes along for the ride.
Buy Tailwind Mastery
Buy the course
$249one-time payment
Get everything in Tailwind Mastery.
- 2+ hours of video
- 21 lessons
- Private Discord
- Summaries with code
- Unlimited access to course materials
Lifetime membership
Get lifetime access to every Build UI course, including Tailwind Mastery, forever.
- Access to all five Build UI courses
- Full access to all future Build UI courses
- New videos added regularly
- Refactoring videos on React
- Private Discord
- Summaries with code
What's included
Stream or download every video
Watch every lesson directly on Build UI, or download them to watch offline at any time.
Live code demos
Access to a live demo of each lesson that runs directly in your browser.
Private Discord
Chat with Sam, Ryan and other Build UI members about the lessons – or anything else you're working on – in our private server.
Video summaries with code snippets
Quickly reference a lesson's material with text summaries and copyable code snippets.
Source code
Each lesson comes with a GitHub repo that includes a diff of the source code.
Invoices and receipts
Get reimbursed from your employer for becoming a better coder!