Session 2: Tailwind's System, Made Visual in Figma

Session 2 of the Designing & Shipping with shadcn/ui in Figma series, in which Gordon learns to think in tokens and utilities and to translate any Auto Layout frame into Tailwind classes. It covers utility-first thinking - composing classes rather than writing bespoke CSS - and the four scales he lives in: spacing on a 4px base, the color palette, the type scale, and the radius scale, each with tables and SVG rulers. Auto Layout is mapped onto flexbox control by control, with a mapping table and an annotated frame, and a repeatable recipe lets him read any frame off as classes. The scaffolded your-turn build works on the Aster site: he builds the pricing card with Auto Layout and spacing and radius variables, compares an eyeballed rebuild against a token-based one, blocks out the five homepage sections on the scale, and reads the frame's Tailwind aloud. The frame-to-classes pattern comes before every check, and the session includes at least four checks, three traps, and homework.

Subject: shadcn/ui + Figma · 97 slides · applied lesson

Open the interactive version of this deck · Homework for this lesson

What this lesson covers

The lesson, slide by slide

1. Tailwind's System, Made Visual in Figma

Title

Session 2 · Designing & Shipping with shadcn/ui

Stop writing bespoke CSS. Start composing tokens. By the end you can read any Auto Layout frame off as a string of Tailwind classes - for the Aster landing page.

2. What you'll be able to do

Objectives

Last session you set up aster-site and met the three layers. Today the goal is one sentence: think in tokens and utilities, and translate any Auto Layout frame into Tailwind classes.

3. What survived from Session 1: Foundations & the Design-to-Code Mental Model?

Warm-up

Discussion prompt

Before we open Session 2: Tailwind's System, Made Visual in Figma: without looking back, what was the main idea of Session 1: Foundations & the Design-to-Code Mental Model, and what could you do by the end of it that you could not do before?

Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.

Answer:

Session 1 of 6, in 60 slides. It explains what Tailwind, Radix, and shadcn/ui actually are and how they relate, why copying components in and owning the code beats a closed library such as MUI or Ant, and how the Figma kit's Pages, Assets panel, and Variables panel each mirror the code one-to-one. A scaffolded your-turn build assembles an Aster profile card from Card, Avatar, Badge, and Button. The session runs the Aster studio landing-page example that continues across all six sessions.

4. Why a designer should care

Concept

You already design on a grid and reuse spacing values. Tailwind is that instinct, turned into a fixed vocabulary the code shares. Once you speak it, a frame and its code stop being two separate artifacts.

The payoff: when your Figma spacing, type, and radius all sit on the same scale Tailwind uses, handoff becomes translation, not reinterpretation. No more "is that 15 or 16 pixels?" arguments.

5. Break it if you can: Why a designer should care

Counterexample

Discussion prompt

The payoff: when your Figma spacing, type, and radius all sit on the same scale Tailwind uses, handoff becomes translation, not reinterpretation. No more "is that 15 or 16 pixels?" arguments.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

6. Today's roadmap

Concept

Five moves, each one earning the next. We finish by rebuilding pieces of the Aster homepage on the scale.

Utility-first
Compose tiny classes, don't sculpt CSS.
Four scales
Spacing, color, type, radius.
Auto Layout = flexbox
Every control maps to a class.
Read any frame
One recipe, frame to classes.

7. By analogy: Today's roadmap

Analogy

Discussion prompt

Explain Today's roadmap by analogy to something with no shadcn/ui + Figma in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

Five moves, each one earning the next. We finish by rebuilding pieces of the Aster homepage on the scale.

8. Utility-First Thinking

Section

Section 1

9. Styling is composing, not authoring

Concept

The old way: for every element you invent a class name, then hand-write a block of CSS - padding, colors, radius, all bespoke. Two hundred elements, two hundred little decisions to keep in sync.

The utility-first way: you compose an element from a fixed set of tiny, single-purpose classes right on the markup. px-4 always means the same padding. You stop inventing values and start selecting them.

utility class — A tiny, single-purpose class that sets one thing to one scale value - e.g. p-4 (16px padding all sides), gap-2 (8px gap), rounded-md (medium corner radius). You compose many of them on one element.

10. Teach it back: Styling is composing, not authoring

Explain it

Discussion prompt

Explain Styling is composing, not authoring to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

The old way: for every element you invent a class name, then hand-write a block of CSS - padding, colors, radius, all bespoke. Two hundred elements, two hundred little decisions to keep in sync.

11. The same button, two ways

Worked example

Here's an Aster primary button as bespoke CSS - a class name plus a rule you author by hand.

.aster-button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  border-radius: 6px;
  background: var(--primary);
  color: var(--primary-foreground);
}

Now the same button as utilities - no separate stylesheet, every value already on the scale.

<button class="inline-flex items-center gap-2 px-4 py-2
  rounded-md bg-primary text-primary-foreground">
  Get started
</button>
bespoke CSSutility classvalue
display: inline-flexinline-flexinline-flex
gap: 8pxgap-28px
padding: 8px 16pxpy-2 px-48px / 16px
border-radius: 6pxrounded-mdfrom --radius

12. Fill in: utility class for The same button, two ways

Comparison

Comparison matrix

From The same button, two ways: refill the utility class column from what you know. The rest of the table is as it appeared.

bespoke CSSutility classvalue
display: inline-flexinline-flexinline-flex
gap: 8pxgap-28px
padding: 8px 16pxpy-2 px-48px / 16px
border-radius: 6pxrounded-mdfrom --radius

13. Lego bricks, not clay

Intuition

Figure (svg): Lego bricks with utility labels versus a lump of clay

Bespoke CSS is clay - infinite freedom, and every element is a fresh sculpt at a slightly different size. Freedom you didn't want, drift you didn't notice.

Utilities are Lego bricks from a fixed set. You don't get 15px; you snap p-4 (16px). The constraint is the feature - everything lines up because it comes from the same box.

14. What rests on this: Lego bricks, not clay

Socratic

Discussion prompt

Bespoke CSS is clay - infinite freedom, and every element is a fresh sculpt at a slightly different size. Freedom you didn't want, drift you didn't notice.

Suppose that were not true. What is the first thing in Session 2: Tailwind's System, Made Visual in Figma that would stop working?

Hint: Follow it one step downstream. The answer is whatever was quietly relying on it.

Answer:

Utilities are Lego bricks from a fixed set. You don't get 15px; you snap p-4 (16px). The constraint is the feature - everything lines up because it comes from the same box.

15. "But I'll run out of classes" - no

Concept

A fair worry: a fixed set sounds limiting. In practice the set is large and combinatorial - a handful of scale steps across padding, gap, size, color, and radius compose into essentially any layout you'd actually design.

And the discipline is exactly what keeps an interface coherent. When you do need a true one-off, Tailwind allows arbitrary values like p-[15px] - but treat that as a last resort, a signal you've stepped off the system.

16. Utilities read in a fixed order

Concept

One quiet nicety: it doesn't matter what order you write the classes in - p-4 flex and flex p-4 render identically. Tailwind resolves each utility to its own rule; there's no cascade to reason about.

But for humans, keep a consistent reading order - layout first (flex flex-col), then spacing (gap-* p-*), then alignment, then size, then color and radius. That's the same order you'll read a frame in. Habit now, speed later.

17. Compose an Aster nav link

Worked example

Utility-first in miniature. An Aster nav link needs: small padding, a small text size, rounded corners, and a subtle hover. Compose it - don't author a .nav-link rule.

<a class="px-3 py-2 text-sm rounded-md hover:bg-accent">
  Work
</a>

Read left to right: px-3 (12px) py-2 (8px) padding, text-sm label, rounded-md corners, and a state utility hover:bg-accent that only applies on hover.

No stylesheet, no naming, nothing to keep in sync. The next nav link reuses the exact same five classes - so they're identical by construction, not by discipline.

18. Something is wrong here: reinventing a class you already have

Anomaly

Predict first

A student writes this, and it looks reasonable:

You want a bit of horizontal padding, so you open a stylesheet and write a custom class.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: You've re-created what px-4 already does - now there are two ways to say the same thing, and they can drift apart.

Reach for the utility that already exists on the scale.

Why: You've re-created what px-4 already does - now there are two ways to say the same thing, and they can drift apart.

19. Trap: reinventing a class you already have

Trap

The trap

You want a bit of horizontal padding, so you open a stylesheet and write a custom class.

Author .aster-pad { padding: 0 16px; }

Why: You've re-created what px-4 already does - now there are two ways to say the same thing, and they can drift apart.

Apply .aster-pad next to Tailwind classes

Why: The bespoke rule sits outside the scale. A theme change to spacing won't touch it, and reviewers can't read the padding off the markup.

The fix

Reach for the utility that already exists on the scale.

Use px-4

Why: 16px horizontal padding, straight from the spacing scale - no new CSS, no second source of truth.

Read it off the markup

Why: Anyone can see the padding is px-4, and it moves in lockstep with every other px-4 on the site.

20. The Four Scales

Section

Section 2

21. You live in four scales

Concept

Almost every visual decision is one of four choices, and each is a scale - a short ordered list of allowed steps. Learn the four and you've learned most of Tailwind's vocabulary.

Spacing
Padding, gap, size - 4px base.
Color
Named palette tokens.
Type
text-sm through text-4xl.
Radius
rounded-sm/md/lg/xl from --radius.

22. Which is which: You live in four scales

Matching

Match the pairs

From You live in four scales — match each one to what it actually does. The descriptions have been shuffled.

  • c1. Spacing
  • c2. Type
  • c3. Radius
  • b1. Padding, gap, size - 4px base.
  • b2. text-sm through text-4xl.
  • b3. rounded-sm/md/lg/xl from --radius.

Why: Spacing, Type, Radius are easy to tell apart while they are sitting next to their descriptions and much harder afterwards, which is what this checks.

23. Scale 1 - Spacing (the 4px base)

Concept

Figure (svg): Spacing ruler from step 1 (4px) to step 8 (32px)

The spacing scale has one base unit: 0.25rem = 4px. Every step is that unit times the number in the class. So the number is not pixels - it's quarter-rems.

p-4 is step 4 = 16px padding. gap-2 is step 2 = 8px gap. Same scale powers padding, gap, margin, width, height - m-*, w-*, h-*, size-* all read from it.

spacing scale — The ordered list of spacing steps built on a 4px base unit. The class number is multiplied by 4px: 1=4, 2=8, 3=12, 4=16, 6=24, 8=32, 12=48.

24. Take the definitions apart: utility class vs spacing scale

Definition probe

Sort into buckets

Every line below is part of the definition of utility class or of spacing scale — one or the other, never both. Put each where it belongs.

utility class
A tiny, single-purpose class that sets one thing to one scale value - e.g.; p-4 (16px padding all sides), gap-2 (8px gap), rounded-md (medium corner radius).; You compose many of them on one element.
spacing scale
The ordered list of spacing steps built on a 4px base unit.; The class number is multiplied by 4px; 1=4, 2=8, 3=12, 4=16, 6=24, 8=32, 12=48.
b1
A tiny, single-purpose class that sets one thing to one scale value - e.g. p-4 (16px padding all sides), gap-2 (8px gap), rounded-md (medium corner radius). You compose many of them on one element.
b2
The ordered list of spacing steps built on a 4px base unit. The class number is multiplied by 4px: 1=4, 2=8, 3=12, 4=16, 6=24, 8=32, 12=48.

25. Reading the spacing table

Worked example

Keep this table in your head. rem is the source of truth; px is what you see at the default root size.

steprempxexample class
10.25rem4pxp-1 / gap-1
20.5rem8pxgap-2
30.75rem12pxpx-3
41rem16pxp-4
61.5rem24pxpy-6
82rem32pxgap-8
123rem48pxpy-12

Notice the multiply-by-4 shortcut: see p-6, think 6 × 4 = 24px. That mental math is the whole game. And going the other way: 32px seen in a mockup → divide by 4 → 8 → gap-8.

26. What each one costs: Reading the spacing table

Trade off

Comparison matrix

From Reading the spacing table: every row here is a choice with a cost. Fill the example class column, then say which row you would actually pick and what you give up for it.

steprempxexample class
10.25rem4pxp-1 / gap-1
20.5rem8pxgap-2
30.75rem12pxpx-3
41rem16pxp-4
61.5rem24pxpy-6
82rem32pxgap-8
123rem48pxpy-12

27. Why 4px, not 1px

Intuition

A scale in steps of 4 is coarse on purpose. If any value were allowed, two designers - or the same designer on two days - would land on 14, 15, 16 for "the same" spacing, and the interface would quietly wobble.

Snapping everything to multiples of 4 makes spacing decisions instead of guesses. Fewer choices, more consistency. Your eye stops negotiating pixels and starts picking rungs.

28. Something is wrong here: gap-2 is not 2px

Anomaly

Predict first

A student writes this, and it looks reasonable:

You read gap-2 and assume the number is a pixel count - 2px.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: Reads the class number as pixels.

The number is quarter-rems: multiply by 4. gap-2 = 2 × 4 = 8px.

Why: Reads the class number as pixels. A 2px gap is almost invisible - the items nearly touch.

29. Trap: gap-2 is not 2px

Trap

The trap

You read gap-2 and assume the number is a pixel count - 2px.

Set the row gap to 2px in Figma

Why: Reads the class number as pixels. A 2px gap is almost invisible - the items nearly touch.

Compare to code

Why: The code renders an 8px gap, so Figma and code disagree. The mockup looks tighter than the built page.

The fix

The number is quarter-rems: multiply by 4. gap-2 = 2 × 4 = 8px.

Set the row gap to 8px in Figma

Why: 8px is step 2 on the scale - exactly what gap-2 produces.

Compare to code

Why: Figma and code match. Every gap-2 on the page is the same 8px.

30. Spacing the Aster hero, step by step

Worked example

The mockup calls for: 48px between the hero and the section above, 24px between the hero's stacked lines, and 32px of vertical breathing room inside. Convert each by dividing by 4.

mockup value÷ 4class
48px section gap12gap-12
24px line gap6gap-6
32px vertical padding8py-8

So the hero frame reads flex flex-col gap-6 py-8, sitting under a page column that uses gap-12. Every number came from the same 4px scale - divide, don't guess.

31. Scale 2 - Color palette

Concept

Color is also a scale, but a named one. Under the hood Tailwind ships a primitive palette - families like violet with steps violet-50 through violet-950, dark rising with the number.

tokenrough roleclass
violet-100very light tintbg-violet-100
violet-600strong brand violetbg-violet-600
violet-950near-black violettext-violet-950

Foreshadow (Session 3): you rarely name raw colors in real components. You'll wrap them in semantic tokens - bg-primary, text-primary-foreground - so one variable retints the whole app. For now, know the palette exists underneath.

32. What rests on this: Scale 2 - Color palette

Socratic

Discussion prompt

Color is also a scale, but a named one. Under the hood Tailwind ships a primitive palette - families like violet with steps violet-50 through violet-950, dark rising with the number.

Suppose that were not true. What is the first thing in Session 2: Tailwind's System, Made Visual in Figma that would stop working?

Hint: Follow it one step downstream. The answer is whatever was quietly relying on it.

33. Aster's primary, named vs semantic

Worked example

Aster's brand violet is oklch(0.55 0.24 285). You could reference a primitive class close to it, but watch what that costs you later.

<!-- primitive: hard-codes the exact violet everywhere -->
<button class="bg-violet-600 text-white">Get started</button>

The semantic version names the role, not the color. Session 3's whole point: change one variable, retint everything.

<!-- semantic: role-based, retints from one variable -->
<button class="bg-primary text-primary-foreground">Get started</button>

Today, either works visually. The habit to start now: think in roles - "this is the primary action" - even while you're still on the raw palette.

34. Scale 3 - Type

Concept

Figure (svg): Type scale samples from small to large

Font size is a scale too: text-sm, text-base, text-lg, text-xl, then 2xl, 3xl, 4xl. Each step also carries a sensible default line-height, so vertical rhythm comes along for free.

classrempx
text-sm0.875rem14px
text-base1rem16px
text-lg1.125rem18px
text-xl1.25rem20px
text-2xl1.5rem24px
text-3xl1.875rem30px
text-4xl2.25rem36px

35. Watch it run: Scale 3 - Type

Pattern

Step through it

Step through Scale 3 - Type one row at a time. What is driving the change, and what would the row after the last one be?

  1. Step 1: class is text-sm
  2. Step 2: class is text-base
  3. Step 3: class is text-lg
  4. Step 4: class is text-xl
  5. Step 5: class is text-2xl
  6. Step 6: class is text-3xl
  7. Step 7: class is text-4xl

36. Type scale on the Aster hero

Worked example

Aster's hero has three text roles. Instead of picking arbitrary sizes, you pick steps.

<p class="text-sm">Freelance design studio</p>
<h1 class="text-4xl">Design that ships.</h1>
<p class="text-lg">Interfaces built on a real system.</p>
roleclasssize
eyebrowtext-sm14px
headlinetext-4xl36px
subheadtext-lg18px

Three steps apart, instantly legible hierarchy - and every heading on the site can reuse the exact same rungs.

37. Watch it run: Type scale on the Aster hero

Pattern

Step through it

Step through Type scale on the Aster hero one row at a time. What is driving the change, and what would the row after the last one be?

  1. Step 1: role is eyebrow
  2. Step 2: role is headline
  3. Step 3: role is subhead

38. Scale 4 - Radius

Concept

Corner radius is the smallest scale, and it's derived. One variable, --radius (Aster's is 0.625rem), drives the whole set. The classes offset from it.

classdefinitionwith --radius = 0.625rem
rounded-smcalc(--radius - 4px)~6px
rounded-mdcalc(--radius - 2px)~8px
rounded-lgvar(--radius)10px
rounded-xlcalc(--radius + 4px)~14px

Because they're all offsets of one variable, dialing --radius up or down re-rounds every corner in the app at once. You tune softness in a single place.

39. What rests on this: Scale 4 - Radius

Socratic

Discussion prompt

Corner radius is the smallest scale, and it's derived. One variable, --radius (Aster's is 0.625rem), drives the whole set. The classes offset from it.

Suppose that were not true. What is the first thing in Session 2: Tailwind's System, Made Visual in Figma that would stop working?

Hint: Follow it one step downstream. The answer is whatever was quietly relying on it.

Answer:

Because they're all offsets of one variable, dialing --radius up or down re-rounds every corner in the app at once. You tune softness in a single place.

40. One dial, whole app

Intuition

Figure (svg): One radius dial driving three cards at once

The radius scale is the clearest example of a token: a named value other things point at. Nothing hard-codes 10px; everything references --radius through rounded-lg.

token — A named design value stored once and referenced by many places (e.g. --radius, --primary). Change the token and every component that references it updates together.

41. The four scales on one Aster button

Worked example

Every scale shows up at once on a single element. Read the Aster CTA and name which scale each class comes from.

<button class="px-4 py-2 text-sm rounded-md
  bg-primary text-primary-foreground">Get started</button>
classscalemeaning
px-4 py-2spacing16px / 8px padding
text-smtype14px label
rounded-mdradius~8px corners
bg-primarycolorrole: primary

Four scales, one button. Composing across all four - fluently, from the panel - is what "thinking in tokens" feels like.

42. Something is wrong here: mixing up the type and spacing numbers

Anomaly

Predict first

A student writes this, and it looks reasonable:

You want a 16px headline, so you reach for a number-4 class like the spacing scale uses.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: The type scale isn't numbered like spacing - there's no text-4.

Use the named type step. 16px is text-base.

Why: The type scale isn't numbered like spacing - there's no text-4. Sizes are named: sm, base, lg, xl, 2xl...

43. Trap: mixing up the type and spacing numbers

Trap

The trap

You want a 16px headline, so you reach for a number-4 class like the spacing scale uses.

Write text-4

Why: The type scale isn't numbered like spacing - there's no text-4. Sizes are named: sm, base, lg, xl, 2xl...

See nothing apply

Why: text-4 isn't a real class, so the text falls back to the default size. Two different scales, two different naming systems.

The fix

Use the named type step. 16px is text-base.

Write text-base

Why: The type scale uses names (sm/base/lg/xl/2xl...), not the ×4 spacing numbers.

See 16px text

Why: Right scale, right vocabulary. Keep spacing numbers and type names in separate mental buckets.

44. Break it on purpose: mixing up the type and spacing numbers

Break the constraint

Discussion prompt

The rule this trap just fixed:

The type scale uses names (sm/base/lg/xl/2xl...), not the ×4 spacing numbers.

Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?

Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.

Answer:

The type scale isn't numbered like spacing - there's no text-4. Sizes are named: sm, base, lg, xl, 2xl...

45. Auto Layout = Flexbox

Section

Section 3 · the heart of it

46. The one mapping that unlocks the rest

Concept

Here is the sentence to internalize: a Figma Auto Layout frame is a CSS flexbox. Every knob in the Auto Layout panel corresponds to a flexbox property, and every flexbox property has a Tailwind class.

Auto Layout — Figma's system for frames that arrange children in a row or column with a set gap, padding, alignment, and resizing behavior - the exact model CSS flexbox uses, control for control.

So when you build with Auto Layout, you're already thinking in flexbox. Reading it off as Tailwind is just looking up each control in one table.

47. Main axis vs cross axis (get this first)

Concept

Figure (svg): Flex row showing main axis maps to justify and cross axis maps to items

Two axes, and mixing them up is the classic error. The main axis runs in the layout direction. The cross axis is perpendicular to it.

justify-* positions children along the main axis. items-* aligns them along the cross axis. In a row: justify-* is horizontal, items-* is vertical. In a column they swap - because the main axis is now vertical.

48. What rests on this: Main axis vs cross axis (get this first)

Socratic

Discussion prompt

Two axes, and mixing them up is the classic error. The main axis runs in the layout direction. The cross axis is perpendicular to it.

Suppose that were not true. What is the first thing in Session 2: Tailwind's System, Made Visual in Figma that would stop working?

Hint: Follow it one step downstream. The answer is whatever was quietly relying on it.

49. The alignment values you'll actually use

Concept

Both justify-* and items-* take the same set of positions. Figma's alignment picker (the nine-dot grid) is choosing exactly these.

intentmain axiscross axis
pack to the startjustify-startitems-start
centerjustify-centeritems-center
pack to the endjustify-enditems-end
push apartjustify-between(n/a)

The Figma nine-dot alignment grid maps cleanly: top-left is justify-start items-start, dead center is justify-center items-center. Read the dot, write the two classes.

50. Where does each piece belong: Session 2: Tailwind's System, Made Visual in…

Sorting

Sort into buckets

These are the pieces of Session 2: Tailwind's System, Made Visual in Figma, out of order. Put each one back under the part of the lesson it belongs to.

Utility-First Thinking
Styling is composing, not authoring; The same button, two ways; Lego bricks, not clay
The Four Scales
You live in four scales; Scale 1 - Spacing (the 4px base); Reading the spacing table
Auto Layout = Flexbox
The one mapping that unlocks the rest; Main axis vs cross axis (get this first); The alignment values you'll actually use
s1
Utility-First Thinking is where Session 2: Tailwind's System, Made Visual in Figma puts Styling is composing, not authoring, The same button, two ways, Lego bricks, not clay. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s2
The Four Scales is where Session 2: Tailwind's System, Made Visual in Figma puts You live in four scales, Scale 1 - Spacing (the 4px base), Reading the spacing table. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s3
Auto Layout = Flexbox is where Session 2: Tailwind's System, Made Visual in Figma puts The one mapping that unlocks the rest, Main axis vs cross axis (get this first), The alignment values you'll actually use. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.

51. The mapping table (live here)

Concept

This is the table to bookmark. Figma control on the left, Tailwind class on the right.

Figma Auto Layout controlTailwind class
Direction: Verticalflex-col
Direction: Horizontalflex-row
Gap (item spacing)gap-* (e.g. gap-4)
Padding (all)p-* (e.g. p-6)
Padding horizontal / verticalpx-* / py-*
Alignment: space betweenjustify-between
Alignment: main axisjustify-start / center / end
Alignment: cross axisitems-start / center / end
Resizing: Hugw-fit / w-auto
Resizing: Fillflex-1 / w-full
Resizing: Fixeda set width, e.g. w-80
Wrapflex-wrap

And the always-there prefix: an Auto Layout frame is flex. So every frame starts flex ... then you fill in direction, gap, padding, alignment, size.

52. An annotated Auto Layout frame

Worked example

Figure (svg): Card frame annotated with its Tailwind classes

Read this frame outside-in. The outer card is a vertical Auto Layout with 24px padding and 16px gap.

<div class="flex flex-col gap-4 p-6"> ... </div>

The top row is horizontal, pushing title and badge apart, vertically centered.

<div class="flex flex-row justify-between items-center">

The button is set to Fill, so it stretches to the card's width.

<button class="flex-1"> ... </button>

53. Something is wrong here: items-* vs justify-*

Anomaly

Predict first

A student writes this, and it looks reasonable:

You want the row's title and badge pushed to opposite ends, so you reach for items-between.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: items-* controls the CROSS axis (vertical, in a row).

Spreading along the row is the main axis, so use justify-*.

Why: items-* controls the CROSS axis (vertical, in a row). There's no items-between, and even items-* wouldn't spread them horizontally.

54. Trap: items-* vs justify-*

Trap

The trap

You want the row's title and badge pushed to opposite ends, so you reach for items-between.

Write flex-row items-between

Why: items-* controls the CROSS axis (vertical, in a row). There's no items-between, and even items-* wouldn't spread them horizontally.

Result

Why: Nothing spreads apart - the class doesn't exist or targets the wrong axis. The layout ignores your intent.

The fix

Spreading along the row is the main axis, so use justify-*.

Write flex-row justify-between items-center

Why: justify-between spreads on the main axis (horizontal); items-center handles cross-axis (vertical) alignment.

Result

Why: Title left, badge right, both vertically centered - exactly "space between" from the Figma panel.

55. Padding: all sides, or axis by axis

Concept

Figma lets you set one padding for all sides, or separate horizontal and vertical values. Tailwind mirrors that exactly with prefixes.

Figma paddingTailwindsets
all sides 16p-4top, right, bottom, left
horizontal 24px-6left + right
vertical 16py-4top + bottom
one side (top 32)pt-8top only

So a navbar with 24px sides and 16px top/bottom is px-6 py-4 - and you can read that straight off the Figma padding fields. Same scale numbers, just split by axis.

56. Fill in: Tailwind for Padding: all sides, or axis by axis

Comparison

Comparison matrix

From Padding: all sides, or axis by axis: refill the Tailwind column from what you know. The rest of the table is as it appeared.

Figma paddingTailwindsets
all sides 16p-4top, right, bottom, left
horizontal 24px-6left + right
vertical 16py-4top + bottom
one side (top 32)pt-8top only

57. Reading a full Auto Layout panel

Worked example

Here's what the Figma Auto Layout panel shows for the Aster hero, and the class each field becomes. This is the panel-to-class translation, field by field.

panel fieldvalueclass
Directionverticalflex-col
Gap between items24gap-6
Horizontal padding24px-6
Vertical padding48py-12
Alignmenttop-centeritems-center justify-start

Line the classes up in reading order and the hero is done: flex flex-col gap-6 px-6 py-12 items-center. Every field on the panel had a home.

58. Resizing: Hug, Fill, Fixed

Concept

Figma's three resizing modes each have a flex meaning. Hug shrinks to content. Fill grows to share the space. Fixed is a hard number.

Figma resizingmeansTailwind
Hug contentsas small as the contentw-fit / w-auto
Fill containerstretch to share spaceflex-1 / w-full
Fixed widthan exact widthw-80 (= 320px)

flex-1 is the workhorse: it says "take a share of the leftover space." Two flex-1 siblings split the row evenly - the flexible behavior a Fixed width can never give you.

59. Teach it back: Resizing: Hug, Fill, Fixed

Explain it

Discussion prompt

Explain Resizing: Hug, Fill, Fixed to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

Figma's three resizing modes each have a flex meaning. Hug shrinks to content. Fill grows to share the space. Fixed is a hard number.

60. Something is wrong here: Fixed width where Fill was meant

Anomaly

Predict first

A student writes this, and it looks reasonable:

You want the Aster pricing cards to share the row evenly, but you set each to a Fixed w-80.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: 320px is frozen. On a wider container the cards stay 320px and leave an awkward gap; on a narrower one they overflow.

You meant Fill - each card takes an equal share of the row.

Why: 320px is frozen. On a wider container the cards stay 320px and leave an awkward gap; on a narrower one they overflow.

61. Trap: Fixed width where Fill was meant

Trap

The trap

You want the Aster pricing cards to share the row evenly, but you set each to a Fixed w-80.

Give each card w-80

Why: 320px is frozen. On a wider container the cards stay 320px and leave an awkward gap; on a narrower one they overflow.

Resize the window

Why: The cards don't respond - Fixed widths ignore available space, so the layout breaks at other sizes.

The fix

You meant Fill - each card takes an equal share of the row.

Give each card flex-1

Why: flex-1 tells each card to grow and share the leftover space equally with its siblings.

Resize the window

Why: The cards stretch and shrink together, always filling the row - responsive by construction.

62. Which of these survive contact with Session 2: Tailwind's System, Made Visual in…?

Two truths and a lie

Sort into buckets

Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.

Holds up
Five moves, each one earning the next. We finish by rebuilding pieces of the Aster homepage on the scale.; Bespoke CSS is clay - infinite freedom, and every element is a fresh sculpt at a slightly different size. Freedom you didn't want, drift you didn't notice.; The spacing scale has one base unit: 0.25rem = 4px. Every step is that unit times the number in the class. So the number is not pixels - it's quarter-rems.
Breaks
You want a bit of horizontal padding, so you open a stylesheet and write a custom class.; You read gap-2 and assume the number is a pixel count - 2px.
sound
These are stated as this lesson states them — each one survives the edge cases Session 2: Tailwind's System, Made Visual in Figma puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

63. Wrap - when a row runs out of room

Concept

Figma's Auto Layout can let a horizontal frame wrap its children onto a new line when they don't fit. That maps to one class: flex-wrap.

It's how a feature grid stays graceful on narrow screens without a media query: the cards simply flow to the next row. You'll reach for it on Aster's feature grid so three cards become two-plus-one when the viewport shrinks.

64. By analogy: Wrap - when a row runs out of room

Analogy

Discussion prompt

Explain Wrap - when a row runs out of room by analogy to something with no shadcn/ui + Figma in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

Figma's Auto Layout can let a horizontal frame wrap its children onto a new line when they don't fit. That maps to one class: flex-wrap.

65. Frames nest, so flex nests

Intuition

Figure (svg): Nested Auto Layout frames, each its own flex container

Real layouts are frames inside frames. A card is a column; its header is a row; its feature list is another column. Each Auto Layout frame is its own independent flex container.

That's why you read outside-in: give the outer frame its classes, then step into each child frame and start the recipe again. The nesting in Figma is exactly the nesting of your divs.

66. Break it if you can: Frames nest, so flex nests

Counterexample

Discussion prompt

Real layouts are frames inside frames. A card is a column; its header is a row; its feature list is another column. Each Auto Layout frame is its own independent flex container.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

67. Worked example: Aster pricing card, line by line

Worked example

Let's read a whole Aster pricing card's Auto Layout off as classes, one control at a time. The card is a vertical frame, 24px padding, 24px gap between blocks, medium-large corners.

<div class="flex flex-col gap-6 p-6 rounded-xl">

Inside, a header row: plan name on the left, "Popular" badge on the right, centered vertically - the space-between pattern.

  <div class="flex flex-row items-center justify-between">
    <span class="text-lg">Pro</span>
    <span class="text-sm">Popular</span>
  </div>

The price, big; and a features list stacked with a small gap.

  <p class="text-4xl">$55</p>
  <div class="flex flex-col gap-2"> ...features... </div>

And the CTA button set to Fill, so it spans the card.

  <button class="flex-1 px-4 py-2 rounded-md
    bg-primary text-primary-foreground">Choose Pro</button>
</div>
frame partAuto Layoutclasses
cardvertical, pad 24, gap 24flex flex-col gap-6 p-6 rounded-xl
headerhorizontal, space betweenflex flex-row items-center justify-between
featuresvertical, gap 8flex flex-col gap-2
CTAFillflex-1 px-4 py-2 rounded-md

68. Read Any Frame

Section

Section 4 · the recipe

69. From "look it up" to "read it off"

Concept

You now have the mapping table. But you don't want to hunt through it every time - you want a fixed order you always walk a frame in, so nothing gets missed and the class string comes out consistent.

Six controls, always the same sequence. Next slide is the recipe; after that we'll drill it until it's automatic.

70. Rebuild the recipe: Pattern: translate a frame to classes

Ranking

Put in order

These are the steps of Pattern: translate a frame to classes, scrambled. Put them back in order before the next slide shows you.

  1. Direction - vertical or horizontal? → flex flex-col or flex flex-row.
  2. Wrap - can children wrap? → add flex-wrap if yes.
  3. Gap - item spacing on the scale → gap-*.
  4. Padding - all / horizontal / vertical on the scale → p-* / px-* / py-*.
  5. Alignment - main axis → justify-*; cross axis → items-*.
  6. Size - each child Hug / Fill / Fixed → w-fit / flex-1 / a set width.

Why: This is the order the recipe itself gives. Recalling the sequence without the slide in front of you is the difference between recognising the method and being able to run it — most of what goes wrong in practice is a step done out of turn.

71. Pattern: translate a frame to classes

Pattern

Every frame, every time, in the same order. Say each control out loud and write its class before moving on.

  1. Direction - vertical or horizontal? → flex flex-col or flex flex-row.
  2. Wrap - can children wrap? → add flex-wrap if yes.
  3. Gap - item spacing on the scale → gap-*.
  4. Padding - all / horizontal / vertical on the scale → p-* / px-* / py-*.
  5. Alignment - main axis → justify-*; cross axis → items-*.
  6. Size - each child Hug / Fill / Fixed → w-fit / flex-1 / a set width.

Direction, Wrap, Gap, Padding, Alignment, Size. Do it outside-in: the frame first, then recurse into each child that is itself an Auto Layout frame.

72. Where does it stop working: Pattern: translate a frame to classes

Edge cases

Discussion prompt

Pattern: translate a frame to classes works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".

Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.

Answer:

Every frame, every time, in the same order. Say each control out loud and write its class before moving on.

73. Run the recipe on the Aster navbar

Worked example

The Aster navbar: a horizontal bar, logo on the left, nav links on the right, 16px vertical + 24px horizontal padding, centered. Run the six steps.

stepreadingclass
Directionhorizontalflex flex-row
Wrapno(none)
Gap24px between groupsgap-6
Padding16 vertical, 24 horizontalpy-4 px-6
Alignmentspace between, centeredjustify-between items-center
Sizebar fills widthw-full

Concatenate the right column and you have the navbar.

<nav class="flex flex-row gap-6 py-4 px-6
  justify-between items-center w-full">

74. Run the recipe on the feature grid

Worked example

Second rep, harder frame. Aster's feature grid: a horizontal row of three cards, wraps on narrow screens, 24px gap, 24px horizontal padding, each card set to Fill.

stepreadingclass
Directionhorizontalflex flex-row
Wrapyesflex-wrap
Gap24pxgap-6
Padding24 horizontalpx-6
Alignmenttop-aligneditems-start
Size (each card)Fillflex-1

The frame, plus each card's size class.

<section class="flex flex-row flex-wrap gap-6 px-6 items-start">
  <div class="flex-1"> ...card... </div>
  <div class="flex-1"> ...card... </div>
  <div class="flex-1"> ...card... </div>
</section>

75. Run the recipe on the footer

Worked example

Third rep, and notice the footer holds two child groups - a copyright block on the left, a links row on the right - so you recurse once. Outer footer first.

stepreadingclass
Directionhorizontalflex flex-row
Wrapno(none)
Gaphandled by space-between(none)
Padding24 horizontal, 32 verticalpx-6 py-8
Alignmentspace between, centeredjustify-between items-center
Sizefull widthw-full

Then step into the right-hand links group - itself a horizontal frame with an 8px gap - and run the recipe again.

<footer class="flex flex-row justify-between items-center px-6 py-8 w-full">
  <span class="text-sm">© Aster</span>
  <div class="flex flex-row gap-2"> ...links... </div>
</footer>

76. Rule out three: Check: the spacing scale

Elimination

Eliminate the wrong options

In Tailwind's default spacing scale, how many pixels is gap-2?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. 2px
  • B. 8px
  • C. 16px
  • D. 0.2rem

Survives elimination: B

Why: The base unit is 0.25rem = 4px, and the class number multiplies it. gap-2 = 2 × 4px = 8px. The number is quarter-rems, not pixels.

77. Check: the spacing scale

Check

The multiply-by-4 rule, straight up.

Check your understanding

In Tailwind's default spacing scale, how many pixels is gap-2?

  • A. 2px
  • B. 8px (correct)
  • C. 16px
  • D. 0.2rem

Answer: B

Why: The base unit is 0.25rem = 4px, and the class number multiplies it. gap-2 = 2 × 4px = 8px. The number is quarter-rems, not pixels.

Why A tempts people
Reads the 2 as pixels. The scale number is not a pixel count - it's multiplied by the 4px base, so 2 means 8px.
Why C tempts people
That's gap-4 (4 × 4px = 16px). gap-2 is one scale step smaller at 8px.
Why D tempts people
Mixes up the unit. gap-2 is 0.5rem (8px); 0.2rem isn't a step on the scale at all.

78. Answer it before you see the options: Check: which axis?

Prediction

Predict first

In a flex-row, you want the children spread to opposite ends along the row (left and right). Which class does that?

Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.

Correct: justify-between

Why: Spreading along the row is the main axis, and justify-* controls the main axis. justify-between pushes the first and last children to the ends with the space distributed between them.

79. Check: which axis?

Check

A horizontal Auto Layout row. Read the prompt carefully - direction matters.

Check your understanding

In a flex-row, you want the children spread to opposite ends along the row (left and right). Which class does that?

  • A. items-between
  • B. items-center
  • C. justify-between (correct)
  • D. flex-wrap

Answer: C

Why: Spreading along the row is the main axis, and justify-* controls the main axis. justify-between pushes the first and last children to the ends with the space distributed between them.

Why A tempts people
items-between isn't a real class, and items-* controls the cross axis (vertical in a row) - it can't spread children horizontally.
Why B tempts people
items-center aligns children on the cross axis (vertically centers them). It doesn't move them apart along the row.
Why D tempts people
flex-wrap lets children wrap onto a new line when they run out of room; it doesn't distribute space between them on one line.

80. How sure are you: Check: Hug, Fill, Fixed

Commit first

Predict first

In Figma terms, which resizing mode - and its Tailwind class - makes each button share the row equally?

Commit to an answer, then rate it — certain, fairly sure, or guessing — and write the rating down before you turn the page.

Correct: Fill → flex-1

Why: Fill means 'grow to share the space', which maps to flex-1. Two flex-1 siblings each take an equal share of the row and resize with the container - exactly the behavior asked for.

The rating matters as much as the answer: confident-and-wrong is the combination that survives revision, because nothing about it feels like it needs revisiting.

81. Check: Hug, Fill, Fixed

Check

You have two buttons in a row and want them to split the width evenly, resizing with the container.

Check your understanding

In Figma terms, which resizing mode - and its Tailwind class - makes each button share the row equally?

  • A. Fixed → w-80
  • B. Fill → flex-1 (correct)
  • C. Hug → w-fit
  • D. Fill → w-fit

Answer: B

Why: Fill means 'grow to share the space', which maps to flex-1. Two flex-1 siblings each take an equal share of the row and resize with the container - exactly the behavior asked for.

Why A tempts people
Fixed (w-80) freezes each button at 320px. They won't share the row or resize with the container.
Why C tempts people
Hug (w-fit) shrinks each button to its own content, so they won't stretch to fill or split the row evenly.
Why D tempts people
Fill is right, but w-fit is the Hug class, not the Fill class. Fill maps to flex-1 (or w-full), not w-fit.

82. Answer it before you see the options: Check: read the frame

Prediction

Predict first

Vertical Auto Layout, 16px gap, 24px padding all sides. Which Tailwind string matches?

Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.

Correct: flex flex-col gap-4 p-6

Why: Vertical → flex-col. 16px is step 4 (4 × 4px) → gap-4. 24px is step 6 (6 × 4px) → p-6. So flex flex-col gap-4 p-6.

83. Check: read the frame

Check

A frame: vertical Auto Layout, 16px gap, 24px padding all around. Which class string is right?

Check your understanding

Vertical Auto Layout, 16px gap, 24px padding all sides. Which Tailwind string matches?

  • A. flex flex-col gap-4 p-6 (correct)
  • B. flex flex-row gap-4 p-6
  • C. flex flex-col gap-16 p-24
  • D. flex flex-col gap-2 p-4

Answer: A

Why: Vertical → flex-col. 16px is step 4 (4 × 4px) → gap-4. 24px is step 6 (6 × 4px) → p-6. So flex flex-col gap-4 p-6.

Why B tempts people
flex-row is horizontal, but the frame is vertical. The gap and padding values are right; only the direction is wrong - it should be flex-col.
Why C tempts people
Treats the pixel values as the class numbers. gap-16 is 64px and p-24 is 96px - the scale multiplies by 4, so 16px is gap-4 and 24px is p-6.
Why D tempts people
gap-2 is 8px and p-4 is 16px - both one step too small. 16px gap is gap-4 and 24px padding is p-6.

84. Build It: Aster on the Scale

Section

Section 5 · Your turn

85. The build: three milestones

Concept

Now you drive. Do each milestone in Figma with real Auto Layout frames and variables, then read your frame back as classes using the recipe. Build first, peek at the solution second.

#milestoneyou produce
1Aster pricing cardan Auto Layout card on spacing + radius variables
2Eyeball vs tokenstwo rebuilds, one comparison
3Homepage blockoutfive section frames on the scale

86. What each one costs: The build: three milestones

Trade off

Comparison matrix

From The build: three milestones: every row here is a choice with a cost. Fill the milestone column, then say which row you would actually pick and what you give up for it.

#milestoneyou produce
1Aster pricing cardan Auto Layout card on spacing + radius variables
2Eyeball vs tokenstwo rebuilds, one comparison
3Homepage blockoutfive section frames on the scale

87. Milestone 1 - build the Aster pricing card

Worked example

Your turn: build the Aster "Pro" pricing card as a vertical Auto Layout frame. Use spacing variables for padding (24) and gap (24), and a radius variable for the corners. Header row: plan name left, badge right. Then price, a features list (8px gap), and a Fill-width CTA.

Hint: outer frame = flex flex-col, padding 24 → p-6, gap 24 → gap-6, corners → rounded-xl. The header row is its own horizontal frame with space-between. The CTA is set to Fill.

<div class="flex flex-col gap-6 p-6 rounded-xl">
  <div class="flex flex-row items-center justify-between">
    <span class="text-lg">Pro</span>
    <span class="text-sm">Popular</span>
  </div>
  <p class="text-4xl">$55</p>
  <div class="flex flex-col gap-2">
    <span class="text-sm">Unlimited projects</span>
    <span class="text-sm">Priority support</span>
  </div>
  <button class="flex-1 px-4 py-2 rounded-md
    bg-primary text-primary-foreground">Choose Pro</button>
</div>

Self-check - your frame should read off as these classes, part by part:

partresulting classes
cardflex flex-col gap-6 p-6 rounded-xl
header rowflex flex-row items-center justify-between
pricetext-4xl
featuresflex flex-col gap-2
CTAflex-1 px-4 py-2 rounded-md bg-primary text-primary-foreground

88. Milestone 2 - eyeball vs tokens

Worked example

Your turn, part A: rebuild the same card by eye - nudge padding, gap, and text sizes until they "look right," reading nothing from the scale. Write down the raw px you land on.

Your turn, part B: rebuild it again using only scale tokens - snap every value to a spacing step, a type step, a radius step. Now put the two side by side.

Hint: for each eyeballed value, find the nearest scale step and note whether it maps cleanly. Off-scale values (13, 15, 18, 22...) are the ones with no clean class.

propertyeyeballednearest tokenclean class?
padding15px16pxyes → p-4
gap10px8pxno → drifts to gap-2
price size34px36pxyes → text-4xl
corner9px10pxyes → rounded-lg

The lesson lands here: the eyeballed version needs arbitrary values and won't align with the rest of the page; the token version is all clean classes that share rhythm with every other Aster frame. Same look, wildly different maintainability.

89. Fill in: nearest token for Milestone 2 - eyeball vs tokens

Comparison

Comparison matrix

From Milestone 2 - eyeball vs tokens: refill the nearest token column from what you know. The rest of the table is as it appeared.

propertyeyeballednearest tokenclean class?
padding15px16pxyes → p-4
gap10px8pxno → drifts to gap-2
price size34px36pxyes → text-4xl
corner9px10pxyes → rounded-lg

90. Milestone 3 - block out the five sections

Worked example

Your turn: block out the whole Aster homepage as five stacked Auto Layout frames - navbar, hero, feature grid, pricing, footer - using only spacing-scale gaps and padding. Don't style yet; just get the frames and rhythm right.

Hint: the page itself is a vertical frame (flex flex-col) with a large gap between sections. Each section is its own Auto Layout frame with its own padding. The feature grid and pricing are horizontal rows of Fill cards.

<div class="flex flex-col gap-12">
  <nav    class="flex flex-row justify-between items-center px-6 py-4"> ... </nav>
  <header class="flex flex-col gap-6 px-6 py-12"> ... </header>
  <section class="flex flex-row gap-6 px-6"> ...feature cards... </section>
  <section class="flex flex-row gap-6 px-6"> ...pricing cards... </section>
  <footer class="flex flex-row justify-between px-6 py-8"> ... </footer>
</div>

Self-check - every gap and padding below is a real scale step. If any of your frames used an off-scale value, snap it to the nearest row here.

sectiondirectiongap / padding classes
pageverticalflex flex-col gap-12
navbarhorizontalpx-6 py-4 justify-between
heroverticalflex flex-col gap-6 px-6 py-12
feature gridhorizontalflex flex-row gap-6 px-6
pricinghorizontalflex flex-row gap-6 px-6
footerhorizontalpx-6 py-8 justify-between

91. Which is which, by direction

Discrimination

Sort into buckets

Sort these by direction, from memory, without looking back at Milestone 3 - block out the five sections. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.

vertical
page; hero
horizontal
navbar; feature grid; pricing; footer
g1
direction is "vertical" for page, hero — that is what the table on "Milestone 3 - block out the five…" records, and it is the single property separating this group from the rest.
g2
direction is "horizontal" for navbar, feature grid, pricing, footer — that is what the table on "Milestone 3 - block out the five…" records, and it is the single property separating this group from the rest.

92. Show it off - read your frame aloud

Worked example

Your turn: pick any frame you built and read it out loud using the recipe - "horizontal, no wrap, gap-6, px-6 py-4, justify-between items-center, full width." If you can say it, you can ship it.

This is the whole skill in one sentence: you now look at an Auto Layout frame and hear its Tailwind. That fluency is what makes handoff feel like reading, not translating.

If your hero reads off as flex flex-col gap-6 px-6 py-12 without checking a table - you're thinking in tokens and utilities. That was today's goal.

93. Rule out three: Check: off-scale padding

Elimination

Eliminate the wrong options

Your Figma frame has 15px padding. What should you do before handoff?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. Keep 15px and ship p-[15px] - it's exact.
  • B. Snap to 16px and use p-4, the nearest scale step.
  • C. Round to 20px and use p-5 - bigger is safer.
  • D. Use p-15, since the class number is the pixel count.

Survives elimination: B

Why: 15px isn't on the 4px scale. Snapping to 16px (step 4) gives a clean p-4 that shares rhythm with the rest of the page and survives a theme's spacing changes - the tiny visual difference isn't worth breaking the system.

94. Check: off-scale padding

Check

You eyeballed a section's padding to 15px. What's the right move before handoff?

Check your understanding

Your Figma frame has 15px padding. What should you do before handoff?

  • A. Keep 15px and ship p-[15px] - it's exact.
  • B. Snap to 16px and use p-4, the nearest scale step. (correct)
  • C. Round to 20px and use p-5 - bigger is safer.
  • D. Use p-15, since the class number is the pixel count.

Answer: B

Why: 15px isn't on the 4px scale. Snapping to 16px (step 4) gives a clean p-4 that shares rhythm with the rest of the page and survives a theme's spacing changes - the tiny visual difference isn't worth breaking the system.

Why A tempts people
p-[15px] is an arbitrary value - it works but signals you've stepped off the system. It won't align with the page's other spacing and defeats the point of the scale.
Why C tempts people
20px is a valid step (p-5), but it's the farther one from 15px. Snapping should go to the nearest step, which is 16px (p-4).
Why D tempts people
The class number is quarter-rems, not pixels. p-15 would be 15 × 4 = 60px, wildly larger than intended.

95. Recap & Homework

Section

Wrap-up

96. Connect it up: Session 2: Tailwind's System, Made Visual in Figma

Connect it up

Draw it

One page, no notation unless you need it: draw how these connect — Utility-First Thinking · The Four Scales · Auto Layout = Flexbox · Read Any Frame · Build It: Aster on the Scale · Recap & Homework. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.

97. What you can do now

Recap

FigmaTailwind
vertical / horizontalflex-col / flex-row
item spacinggap-* (×4px)
paddingp-* / px-* / py-*
space betweenjustify-between
cross-axis alignitems-*
Hug / Fill / Fixedw-fit / flex-1 / set width

Homework: block out the full Aster homepage - navbar, hero, feature grid, pricing, footer - as Auto Layout frames using only spacing-scale gaps. No off-scale values. Bring the frames next time.

Next session (Session 3): semantic tokens - trade the raw palette for --primary, --muted, and friends, so one variable retints all of Aster at once.

Sources

  1. Tailwind CSS Docs - Utility-First Fundamentals, Spacing & Sizing scale
  2. Tailwind CSS Docs - Font Size (type scale) and Border Radius
  3. Figma Help Center - Explore Auto Layout properties (direction, gap, padding, alignment, resizing)
  4. shadcn/ui Docs - Theming (tokens, --radius, globals.css)
  5. MDN - CSS Flexbox (justify-content vs align-items, main vs cross axis)

Want this taught 1-on-1? Alexander tutors shadcn/ui + Figma — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108