RizTech Academy logo
RizTech Academy
Building the InterfaceLesson 1 of 525 min

Styling with Tailwind

Tailwind is a set of small CSS classes you combine in your markup instead of writing CSS files. This lesson is a complete working reference — you should not need to leave it to style anything in this course.

The idea

<button className="rounded-lg bg-emerald-600 px-4 py-2 font-medium text-white hover:bg-emerald-700">
  Add to cart
</button>

Each class does one thing: rounded-lg sets a border radius, px-4 sets horizontal padding, bg-emerald-600 sets a background colour.

The first reaction is that this is ugly, and it is noisier than a class name like .btn-primary. What you get for it:

Styles cannot leak. There is no global stylesheet where a rule from another page overrides yours.

Deleting a component deletes its styles. No orphaned CSS accumulating for years.

No naming. A large share of CSS effort goes into inventing class names, and this removes it.

Consistency by default. p-4 is always 16px. Nobody types padding: 15px by accident, so a design stays even without discipline.

The cost is verbose markup, and the answer to that is components — you write the button once.

The scale

Almost every spacing and sizing number is a step on one scale. 1 unit = 0.25rem = 4px.

Class Value
p-0 0
p-0.5 2px
p-1 4px
p-2 8px
p-3 12px
p-4 16px
p-6 24px
p-8 32px
p-12 48px
p-16 64px

Knowing 4 = 16px lets you work out any of them.

Spacing

Prefix Sets
p-* padding, all sides
px-* py-* padding horizontal / vertical
pt-* pr-* pb-* pl-* padding one side
m-* mx-* mt-* … margin, same pattern
-mt-4 negative margin
gap-* gap between flex or grid children
space-y-4 vertical space between children

Prefer gap over margins in flex and grid layouts. Margins collapse, need clearing on the last item, and fight you; gap does not.

Layout

<div className="flex items-center justify-between gap-4">
<div className="grid grid-cols-3 gap-6">
Class Does
flex flex row
flex-col flex column
items-center cross-axis centre
items-start items-end cross-axis start / end
justify-center main-axis centre
justify-between space between
flex-1 grow to fill
shrink-0 never shrink
flex-wrap allow wrapping
grid grid
grid-cols-4 four equal columns
col-span-2 span two columns
hidden display: none
block inline-block display

shrink-0 matters more than it looks — a flex child containing a long word will squash its siblings without it.

Sizing

Class Does
w-full h-full 100%
w-screen h-screen viewport
w-4 h-10 the spacing scale
max-w-7xl 80rem — a typical page width
min-h-screen at least full height
aspect-square 1:1
size-10 width and height together

Colour

The pattern is {property}-{colour}-{shade}, with shades from 50 (lightest) to 950 (darkest).

bg-emerald-600      text-gray-900      border-gray-200
Use Typical
Page background bg-white, bg-gray-50
Body text text-gray-900
Secondary text text-gray-600
Muted text text-gray-500
Borders border-gray-200
Primary action bg-emerald-600
Destructive bg-red-600, text-red-600
Success text-emerald-600

Opacity with a slash: bg-black/50, text-white/70.

Keep to a small set. Three greys, one brand colour and red for errors covers most of a shop. A palette that grows uncontrolled is how interfaces start looking untidy.

Text

Class Value
text-xs 12px
text-sm 14px
text-base 16px
text-lg 18px
text-xl 20px
text-2xl 24px
text-3xl 30px
font-medium font-semibold font-bold weight
text-center text-right alignment
leading-tight leading-relaxed line height
truncate one line with an ellipsis
line-clamp-2 two lines then ellipsis
tabular-nums equal-width digits

line-clamp-2 is what keeps product titles from breaking a grid. tabular-nums stops prices jittering in a list.

Borders, corners, shadows

Class Does
border 1px, all sides
border-2 border-t width / one side
rounded rounded-lg rounded-xl 4 / 8 / 12px
rounded-full pill or circle
shadow-sm shadow shadow-lg elevation
ring-1 ring-gray-200 outline that does not affect layout

ring is better than border for focus states, because adding a border changes an element's size and shifts the layout.

State and responsive prefixes

Any class can be prefixed:

<button className="bg-emerald-600 hover:bg-emerald-700 focus-visible:ring-2 disabled:opacity-50">
Prefix When
hover: pointer over it
focus: focused
focus-visible: focused by keyboard only
active: being pressed
disabled: disabled
group-hover: a parent with group is hovered
sm: ≥ 640px
md: ≥ 768px
lg: ≥ 1024px
xl: ≥ 1280px
dark: dark mode

Breakpoints are minimum widths, so unprefixed classes are the mobile styles.

<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4 lg:gap-6">

Two columns on a phone, three on a tablet, four on a laptop. This is mobile-first and it is the right way round: most of your customers are on a phone.

Use focus-visible: rather than focus: for rings, so they appear for keyboard users and not on every mouse click.

Conditional classes

<button className={`rounded-lg px-4 py-2 ${isActive ? "bg-emerald-600 text-white" : "bg-gray-100 text-gray-700"}`}>

Template literals work and get unreadable quickly. clsx is the standard fix:

npm install clsx --workspace=apps/web
import clsx from "clsx";

<button
  className={clsx(
    "rounded-lg px-4 py-2 font-medium",
    isActive && "bg-emerald-600 text-white",
    !isActive && "bg-gray-100 text-gray-700",
    disabled && "cursor-not-allowed opacity-50"
  )}
>

Falsy values are dropped. Note isActive && here is safe because these are strings, not numbers — the JSX 0 trap does not apply inside clsx.

Never build class names dynamically

<div className={`bg-${colour}-600`} />        // produces nothing

Tailwind scans your source as text at build time and generates only the classes it literally finds. bg-${colour}-600 never appears as a string, so that CSS is never generated.

Map to complete class names instead:

const STYLES = {
  success: "bg-emerald-100 text-emerald-800",
  error: "bg-red-100 text-red-800",
} as const;

<span className={STYLES[variant]} />

This is the single most common Tailwind bug. The symptom is a style that works in development and vanishes in production, or simply never applies.

Custom values

<div className="w-[347px] bg-[#1f2937] grid-cols-[200px_1fr]" />

Square brackets take an arbitrary value. Use them sparingly — a design of arbitrary values has lost the consistency that was the point.

For anything reused, put it in the config instead:

// tailwind.config.ts
export default {
  theme: {
    extend: {
      colors: {
        brand: { DEFAULT: "#059669", dark: "#047857" },
      },
    },
  },
};

Then bg-brand and hover:bg-brand-dark work everywhere.

Check your work

Answers to the practice below, so you can verify without a mentor.

A card: rounded-xl border border-gray-200 bg-white p-4 shadow-sm

A primary button: rounded-lg bg-emerald-600 px-4 py-2 font-medium text-white hover:bg-emerald-700 focus-visible:ring-2 focus-visible:ring-emerald-500 disabled:opacity-50

A row with the name left and price right: flex items-center justify-between gap-4 — justify-between is what pushes them apart.

Responsive grid, 2 / 3 / 4 columns: grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4 lg:gap-6

Title clamped to two lines: line-clamp-2

Why bg-${colour}-600 fails: Tailwind reads your files as text at build time and only generates classes that appear literally. That string never exists in the source, so the CSS is never written.

Mobile-first order: unprefixed classes apply at every size; sm: and above override them on wider screens. So write the phone layout first and add prefixes for larger screens.

Practice

  1. Style a card: white background, 1px light border, 12px corners, 16px padding, small shadow.
  2. Style a primary button with hover, keyboard focus ring and a disabled state.
  3. Lay out a row with the product name on the left and price on the right.
  4. Build a grid that is 2 columns on a phone, 3 on a tablet, 4 on a laptop.
  5. Clamp a long product title to two lines.
  6. Write bg-${colour}-600 with colour = "emerald". Confirm nothing happens, then fix it with a lookup object.
  7. Install clsx and use it to switch a button between active and inactive.
  8. Add a brand colour to tailwind.config.ts and use bg-brand.
  9. Resize your browser through all four breakpoints and check nothing overflows horizontally at 320px.

Next: turning these classes into components you write once.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship