A taste of Tailwind

A taste of Tailwind

Get acquainted with Tailwind's utility-first approach by building a custom message component.

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 like gray-950 and blue-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 as size-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

$199one-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

$349
access all coursesone-time payment

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!