Session 5: Composition — Responsive Layouts, Blocks & Pages

Session 5 of the shadcn/ui and Figma series, in which Gordon composes a complete, responsive Aster homepage entirely from his themed components. It teaches the hierarchy that runs from components to blocks to pages, "blocks" being shadcn's own term, then covers responsive design in Figma - the Tailwind breakpoints sm, md, lg, and xl, constraints, minimum and maximum width, and wrapping Auto Layout - and maps all of it onto responsive Tailwind such as grid-cols-1 md:grid-cols-3, flex-col md:flex-row, hidden md:flex, max-w-*, and container. The usual suspects (navbar, hero, feature grid, pricing, and footer) are built as worked examples that read off their own responsive classes, and spacing and type stay on the scale at every breakpoint. The session includes a compose-a-responsive-page pattern, a breakpoint table, four checks, three traps, and a fully scaffolded your-turn build of the Aster homepage at desktop and mobile with a per-section self-check.

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

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

What this lesson covers

The lesson, slide by slide

1. Composition: Responsive Pages

Title

Session 5 · shadcn/ui + Figma

Stack your themed components into blocks, blocks into a page, and make the whole thing reflow from desktop to mobile — Figma constraints and wrapping Auto Layout mapped straight onto Tailwind's breakpoints.

2. What you will be able to do

Objectives

You have themed components and states. Today you assemble them into the real Aster homepage — one that survives a phone. By the end you can:

3. What survived from Session 4: Radix & Component Anatomy?

Warm-up

Discussion prompt

Before we open Session 5: Composition — Responsive Layouts, Blocks & Pages: without looking back, what was the main idea of Session 4: Radix & Component Anatomy, 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 4 of 6 (60 slides): what 'headless' means (Radix supplies behavior + accessibility, shadcn adds the skin), Figma component properties — variants (default/secondary/outline/ghost/destructive) vs boolean props like showIcon and why booleans keep a kit lean, and every interactive state that matters (default, hover, focus-visible ring, active, disabled). Your-turn: brand an Aster button/input and document all its states.

4. Where this session sits

Concept

You already have the pieces: a theme (Session 2), components and variants (Session 3), and states (Session 4). What's missing is the thing a visitor actually loads — a whole page that works at any width.

The mental shift for today: stop thinking in single components and start thinking in arrangements. shadcn ships whole arrangements — it calls them blocks — and a page is just blocks stacked. Figma and Tailwind both give you tools to make those arrangements reflow.

One rule threads through everything: base = mobile, larger widths add overrides. Tailwind is mobile-first, and once you internalise that, responsive design stops being guesswork.

5. Break it if you can: Where this session sits

Counterexample

Discussion prompt

One rule threads through everything: base = mobile, larger widths add overrides. Tailwind is mobile-first, and once you internalise that, responsive design stops being guesswork.

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 stops, each one leaning on the last — ending with the full Aster homepage at two breakpoints.

Blocks & pages
Components → blocks → pages, the shadcn way.
Responsive in Figma
Breakpoints, constraints, wrapping Auto Layout.
The usual suspects
Navbar, hero, feature grid, pricing, footer.
Consistency
Hold type + spacing on the scale everywhere.
Build Aster
Compose the homepage at desktop + mobile.

7. Which is which: Today's roadmap

Matching

Match the pairs

From Today's roadmap — match each one to what it actually does. The descriptions have been shuffled.

  • c1. Blocks & pages
  • c2. Responsive in Figma
  • c3. The usual suspects
  • b1. Components → blocks → pages, the shadcn way.
  • b2. Breakpoints, constraints, wrapping Auto Layout.
  • b3. Navbar, hero, feature grid, pricing, footer.

Why: Blocks & pages, Responsive in Figma, The usual suspects are easy to tell apart while they are sitting next to their descriptions and much harder afterwards, which is what this checks.

8. Components → Blocks → Pages

Section

Section 1

9. The three levels of composition

Concept

Figure (svg): Hierarchy of components inside blocks inside a page

Component: one reusable piece — a Button, a Card, an Input. Block: a themed arrangement of components that does one job — a hero, a pricing section. Page: blocks stacked top to bottom.

block — A themed arrangement of components forming one section of a page — a navbar, hero, or pricing section. shadcn ships ready-made blocks; you compose your own from your themed components.

The whole trick is that a block is built from components that already read your theme. Retint one semantic token and every block retints at once — you never restyle the page.

10. By analogy: The three levels of composition

Analogy

Discussion prompt

Explain The three levels of composition 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:

The whole trick is that a block is built from components that already read your theme. Retint one semantic token and every block retints at once — you never restyle the page.

11. shadcn 'blocks', applied in Figma

Concept

On ui.shadcn.com the Blocks page is a gallery of full sections — dashboards, login screens, sidebars — built entirely from the components you already own. They're not a new primitive; they're recipes for arranging the primitives.

In Figma you mirror that idea: a block is a frame with Auto Layout that contains component instances. In code it's a section of JSX that composes <Button>, <Card>, etc. Same arrangement, two representations.

So the Aster page you'll build is five blocks — navbar, hero, feature grid, pricing, footer — each a frame, stacked in one vertical Auto Layout.

12. A page is a shelf of blocks

Intuition

Think of the page as a vertical shelf. Each block is a tray that slides onto the shelf. You don't reach inside a tray to rearrange forks — you slot the whole tray in, and the tray knows how to arrange its own contents.

That's why composition scales: to add a testimonials section you build one more tray and slot it in. Nothing above or below it needs touching, because each block owns its own internal Auto Layout.

13. The Aster page as a stack of blocks

Worked example

Here's the whole homepage expressed as one vertical stack. Read it top to bottom — that's exactly the order a visitor scrolls.

export default function Home() {
  return (
    <main className="flex flex-col">
      <Navbar />
      <Hero />
      <FeatureGrid />
      <Pricing />
      <Footer />
    </main>
  )
}

flex flex-col is the vertical shelf — the page's own Auto Layout. Each child is a block you'll build next.

blockFigma framejob
Navbartop bar, horizontal Auto Layoutlogo + links + CTA
Herocentered columnheadline + subcopy + button
FeatureGrid3-across gridthree feature cards
Pricingrow of price cardsplans side by side
Footermulti-columnlinks + legal

14. Fill in: Figma frame for The Aster page as a stack of blocks

Comparison

Comparison matrix

From The Aster page as a stack of blocks: refill the Figma frame column from what you know. The rest of the table is as it appeared.

blockFigma framejob
Navbartop bar, horizontal Auto Layoutlogo + links + CTA
Herocentered columnheadline + subcopy + button
FeatureGrid3-across gridthree feature cards
Pricingrow of price cardsplans side by side
Footermulti-columnlinks + legal

15. Why compose this way

Concept

Composing in blocks pays off three ways. Reuse: a themed Card built once appears in the feature grid and the pricing row. Isolation: each block owns its own layout, so editing the hero can't disturb the footer.

Consistency: every block reads the same tokens and the same scale, so the page reads as one design. And because responsiveness lives inside each block, making the page responsive is just making five blocks responsive — a tractable, checkable list.

16. Naming the three levels on Aster

Worked example

Gordon says: "I'll add a testimonials block to Aster." Placing that sentence on the ladder tells you exactly what he's building — one page section, made of components he already owns.

levelon Asterin code
componentone Card, one Button<Card />, <Button />
blockthe testimonials section<Testimonials /> composing Cards
pagethe whole homepage<main> of stacked blocks

So a testimonials block is level two: a themed arrangement of Cards, not a new component and not a whole page. Adding it is slotting one more tray onto the shelf.

17. What each one costs: Naming the three levels on Aster

Trade off

Comparison matrix

From Naming the three levels on Aster: every row here is a choice with a cost. Fill the on Aster column, then say which row you would actually pick and what you give up for it.

levelon Asterin code
componentone Card, one Button<Card />, <Button />
blockthe testimonials section<Testimonials /> composing Cards
pagethe whole homepage<main> of stacked blocks

18. Why own your blocks (vs a locked library)

Concept

A shadcn block is a recipe you copy into your repo and own — not an installed, locked package like a pre-built theme. That's the same ownership model as the components underneath it, and it's what makes composition safe to edit.

Because you own the block's source, tweaking its layout — a wider hero, a fourth pricing card — is just editing your own file. Nothing upstream fights you, and your theme tokens still flow through it.

Keep that in mind as you build: every block is yours to shape, and every block reads the same tokens, so the page stays one coherent design.

19. Teach it back: Why own your blocks (vs a locked library)

Explain it

Discussion prompt

Explain Why own your blocks (vs a locked library) 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:

Because you own the block's source, tweaking its layout — a wider hero, a fourth pricing card — is just editing your own file. Nothing upstream fights you, and your theme tokens still flow through it.

20. Responsive Design in Figma

Section

Section 2 · the core cycle

21. Breakpoints: widths where the layout changes

Concept

A breakpoint is a screen width at which you change the layout. Tailwind ships four, and you'll design against them so your Figma frames and your code agree.

breakpoint — A minimum screen width at which a responsive rule turns on. Tailwind's are sm 640px, md 768px, lg 1024px, xl 1280px. A prefix like md: means 'at this width and wider'.

breakpointmin widthwhat typically changes on Aster
(base)0px (mobile)everything stacked in one column
sm640pxsmall tablets; wider max-w on hero copy
md768pxnav links appear, feature grid goes 3-across
lg1024pxpricing sits in a comfortable row
xl1280pxpage hits its max container width

22. Take the definitions apart: block vs breakpoint

Definition probe

Sort into buckets

Every line below is part of the definition of block or of breakpoint — one or the other, never both. Put each where it belongs.

block
A themed arrangement of components forming one section of a page; a navbar, hero, or pricing section.; shadcn ships ready-made blocks
breakpoint
A minimum screen width at which a responsive rule turns on.; Tailwind's are sm 640px, md 768px, lg 1024px, xl 1280px.; means 'at this width and wider'.
b1
A themed arrangement of components forming one section of a page — a navbar, hero, or pricing section. shadcn ships ready-made blocks; you compose your own from your themed components.
b2
A minimum screen width at which a responsive rule turns on. Tailwind's are sm 640px, md 768px, lg 1024px, xl 1280px. A prefix like md: means 'at this width and wider'.

23. Mobile-first: base is the small screen

Concept

This is the single fact people get backwards. In Tailwind, an unprefixed class is the mobile style, and a prefixed class like md: is an override that turns on at that width and up. There is no mobile: prefix — mobile is the default.

So grid-cols-1 md:grid-cols-3 reads: one column by default (mobile), three columns from 768px up. It does NOT mean 'three columns on mobile'. Get the direction right and every other responsive class becomes obvious.

In Figma you design the same way: sketch the mobile frame first (narrow, everything stacked), then a wider frame that adds the overrides. Designing desktop-first and cramming it down later is how layouts break.

24. Enhance upward, never patch downward

Intuition

Mobile-first has a feel to it: you build the simplest, narrowest version that works, then add capability as the screen grows — links reappear, columns fan out, type gets bigger. You're always enhancing upward.

The opposite — starting rich and desktop-y, then patching downward to cram it onto a phone — is where the off-scale sizes and overflow bugs creep in. Every md: you write should read as 'and on bigger screens, additionally…'.

25. Constraints: how a layer reacts to resizing

Concept

Figure (svg): Constraints keep a logo left, a title centered, and a button right as the frame widens

Constraints tell a layer what to do when its parent frame resizes: pin left/right/top/bottom, center, or scale. A logo pinned left hugs the left edge; a button pinned right rides the right edge as the frame widens.

constraint — A rule on a Figma layer that fixes its position or size relative to a parent edge (left, right, top, bottom), the center, or scales it — controlling how it responds when the frame is resized.

Constraints are the manual tool for plain frames. But most Aster blocks use Auto Layout, where reflow is handled by direction, resizing, and wrapping instead — which is closer to how flexbox and grid actually behave in the browser.

26. Constraints on the Aster navbar

Worked example

Picture the navbar as a plain frame for a moment, to see what constraints buy you. Three layers, three constraints — and the bar behaves at any width.

layerconstraintbehaviour when frame widens
logopin leftstays anchored to the left edge
nav linkscenterstay centered as the bar grows
CTA buttonpin rightrides the right edge outward

With Auto Layout you'd get the same result more directly: justify-between pushes logo and CTA to the edges, no per-layer pinning. Constraints are the fallback when a layer isn't in an Auto Layout flow.

27. Min/max width and Fill vs Fixed

Concept

A block that should span the screen must be Fill (w-full), not a Fixed width. Fixed pins the pixels and refuses to reflow — the fastest way to break responsiveness. Reach for Fixed only for genuinely fixed things like an icon.

To stop text lines getting uncomfortably long on huge screens, cap width with max-width: in Figma set a max on the Auto Layout frame; in Tailwind that's max-w-2xl (or max-w-md, max-w-4xl). The element fills up to that ceiling, then stops.

The container utility bundles this pattern: it centers content and applies sensible max-widths that step up per breakpoint. Wrap your whole page in container mx-auto px-4 and the outer margins take care of themselves.

28. Figma resizing → Tailwind width classes

Worked example

Every Figma resizing choice has a Tailwind class. You already met these for single components — here they govern whole blocks.

Figma resizingmeansTailwind
Fill containergrow to fill the parentw-full or flex-1
Hug contentsshrink to fit childrenw-auto / w-fit
Fixed widtha set pixel widthw-[360px] (avoid on blocks)
Max width on framecap how wide it growsmax-w-2xl
Centered, cappedcentered column with a ceilingcontainer mx-auto

Rule of thumb for blocks: Fill the width, cap with max-w, center with mx-auto. Fixed widths are for icons and avatars, not sections.

29. Something is wrong here: a Fixed-width block that won't reflow

Anomaly

Predict first

A student writes this, and it looks reasonable:

Gordon builds the hero at a comfortable Fixed width of 1200px because that's how it looks on his monitor.

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

Correct: It pins 1200 pixels. On a 375px phone the frame overflows the screen and the page scrolls sideways.

Gordon sets the hero to Fill and caps its inner content with a max-width.

Why: It pins 1200 pixels. On a 375px phone the frame overflows the screen and the page scrolls sideways.

30. Trap: a Fixed-width block that won't reflow

Trap

The trap

Gordon builds the hero at a comfortable Fixed width of 1200px because that's how it looks on his monitor.

Sets the hero frame to Fixed 1200px

Why: It pins 1200 pixels. On a 375px phone the frame overflows the screen and the page scrolls sideways.

In code this is w-[1200px]

Why: A hard pixel width can't shrink; the browser shows a horizontal scrollbar and the content is cut off on mobile.

The fix

Gordon sets the hero to Fill and caps its inner content with a max-width.

Sets the hero frame to Fill container

Why: It grows to the screen width at every size — 375px, 768px, 1280px — with no overflow.

In code this is w-full max-w-6xl mx-auto

Why: Fills up to a 1152px ceiling, then centers. Wide on desktop, edge-to-edge and readable on mobile.

31. Wrapping Auto Layout: rows that reflow to columns

Concept

Figure (svg): A 3-across card row reflowing to a single stacked column on mobile

Turn on wrap on a horizontal Auto Layout and its children flow onto new lines when they run out of room — a row of three cards becomes two-and-one, then a single column, as the frame narrows. In Tailwind flexbox that's flex-wrap.

For a fixed count per breakpoint, a grid is cleaner: grid grid-cols-1 md:grid-cols-3 gives exactly one column on mobile and three from md up. Use wrap when the count can be fluid; use grid when you want a definite layout at each breakpoint.

Either way the reflow is built into the layout, not something you position by hand. That's what makes it survive a screen size you didn't test.

32. What rests on this: Wrapping Auto Layout: rows that reflow to columns

Socratic

Discussion prompt

Either way the reflow is built into the layout, not something you position by hand. That's what makes it survive a screen size you didn't test.

Suppose that were not true. What is the first thing in Session 5: Composition — Responsive Layouts, Blocks & Pages that would stop working?

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

33. Auto Layout is flexbox; the grid is a grid

Intuition

You already know Auto Layout is flexbox: vertical/horizontal → flex-col/flex-row, gap → gap-*, wrap → flex-wrap. Responsive just means swapping that direction at a breakpoint: flex-col md:flex-row is 'stacked on mobile, side by side from md up'.

So the entire responsive toolkit is two moves you already have — change direction (flex-col md:flex-row) or change column count (grid-cols-1 md:grid-cols-3) — gated by a breakpoint prefix. Nothing new to learn, just where to put the md:.

34. Layering breakpoints: base then sm then lg

Concept

You can stack more than one override. Prefixes cascade upward: each applies from its width until a larger one takes over. grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 walks one → two → three columns as the screen grows.

widthactive rulecolumns
<640pxgrid-cols-11
640–1023pxsm:grid-cols-22
≥1024pxlg:grid-cols-33

You rarely need all four breakpoints. For Aster, md: alone carries most blocks — reach for sm:/lg: only where a two-step transition genuinely reads better.

35. Watch it run: Layering breakpoints: base then sm then lg

Pattern

Step through it

Step through Layering breakpoints: base then sm then lg one row at a time. What is driving the change, and what would the row after the last one be?

  1. Step 1: width is <640px
  2. Step 2: width is 640–1023px
  3. Step 3: width is ≥1024px

36. A feature grid reflowing 3 → 1

Worked example

The core responsive class of the whole session. Read it left to right: mobile first, then the md: override.

<section className="grid grid-cols-1 gap-6 md:grid-cols-3">
  <FeatureCard />
  <FeatureCard />
  <FeatureCard />
</section>

grid-cols-1 is the mobile default; md:grid-cols-3 overrides it from 768px up. gap-6 (24px) is unprefixed, so the gap holds at every width.

screen widthactive grid rulelayout
375px (phone)grid-cols-1cards stacked, one column
700px (small tablet)grid-cols-1still one column (below md)
768px+ (md and up)md:grid-cols-3three cards across

37. Something is wrong here: the breakpoint override on the wrong side

Anomaly

Predict first

A student writes this, and it looks reasonable:

Gordon wants three columns on desktop and one on mobile, and writes the prefix on the mobile intention.

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

Correct: This reads: three columns by default (so on mobile too), collapsing to one from 768px up — the exact opposite of what he wants.

Remember mobile-first: the base class is the mobile layout, md: adds the desktop override.

Why: This reads: three columns by default (so on mobile too), collapsing to one from 768px up — the exact opposite of what he wants.

38. Trap: the breakpoint override on the wrong side

Trap

The trap

Gordon wants three columns on desktop and one on mobile, and writes the prefix on the mobile intention.

Writes grid-cols-3 md:grid-cols-1

Why: This reads: three columns by default (so on mobile too), collapsing to one from 768px up — the exact opposite of what he wants.

Result on a phone

Why: Three squished columns on a 375px screen; the cards are unreadable. Desktop shows a single lonely column.

The fix

Remember mobile-first: the base class is the mobile layout, md: adds the desktop override.

Writes grid-cols-1 md:grid-cols-3

Why: One column by default (mobile), three from md up. Base describes the small screen; the prefix upgrades the big one.

Result on a phone

Why: A clean single column on mobile, three tidy cards on desktop — because the unprefixed class is always the mobile case.

39. Break it on purpose: the breakpoint override on the wrong side

Break the constraint

Discussion prompt

The rule this trap just fixed:

One column by default (mobile), three from md up. Base describes the small screen; the prefix upgrades the big one.

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:

This reads: three columns by default (so on mobile too), collapsing to one from 768px up — the exact opposite of what he wants.

40. Rebuild the recipe: How to compose a responsive page

Ranking

Put in order

These are the steps of How to compose a responsive page, scrambled. Put them back in order before the next slide shows you.

  1. Lay out the mobile version first — everything stacked, one column, base (unprefixed) classes only.
  2. Add md:/lg: overrides for wider screens — change direction (md:flex-row) or column count (md:grid-cols-3).
  3. Keep every value on the scale — spacing on the 4px system, type on the type scale; step down, never invent.
  4. Verify at each breakpoint — check the mobile frame, the md frame, and the desktop frame actually look right.

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.

41. How to compose a responsive page

Pattern

Every block you build today — and the whole Aster page — follows the same four moves:

  1. Lay out the mobile version first — everything stacked, one column, base (unprefixed) classes only.
  2. Add md:/lg: overrides for wider screens — change direction (md:flex-row) or column count (md:grid-cols-3).
  3. Keep every value on the scale — spacing on the 4px system, type on the type scale; step down, never invent.
  4. Verify at each breakpoint — check the mobile frame, the md frame, and the desktop frame actually look right.

Say it as a sentence: base is mobile, md: upgrades to desktop, values stay on the scale, verify at each width.

42. Where does it stop working: How to compose a responsive page

Edge cases

Discussion prompt

How to compose a responsive page 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 block you build today — and the whole Aster page — follows the same four moves:

43. Rule out three: Check: reading a responsive class

Elimination

Eliminate the wrong options

A section has className="flex flex-col md:flex-row gap-4". How does it lay out on a 375px phone?

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. A horizontal row, because md:flex-row is listed and takes priority.
  • B. A vertical column, because flex-col is the base (mobile) style and 375px is below md.
  • C. It errors — you can't combine flex-col and flex-row.
  • D. It ignores both and stacks with default block flow.

Survives elimination: B

Why: Tailwind is mobile-first: the unprefixed flex-col is the base style and applies at every width unless overridden. md:flex-row only turns on at 768px and up. At 375px (below md) the base wins, so the section is a vertical column.

44. Check: reading a responsive class

Check

A visitor opens Aster on a 375px-wide phone. Apply the mobile-first rule.

Check your understanding

A section has className="flex flex-col md:flex-row gap-4". How does it lay out on a 375px phone?

  • A. A horizontal row, because md:flex-row is listed and takes priority.
  • B. A vertical column, because flex-col is the base (mobile) style and 375px is below md. (correct)
  • C. It errors — you can't combine flex-col and flex-row.
  • D. It ignores both and stacks with default block flow.

Answer: B

Why: Tailwind is mobile-first: the unprefixed flex-col is the base style and applies at every width unless overridden. md:flex-row only turns on at 768px and up. At 375px (below md) the base wins, so the section is a vertical column.

Why A tempts people
Later in the class list does not mean higher priority here; md:flex-row is width-gated and only activates at 768px+, which 375px is not.
Why C tempts people
The two are meant to be combined — one is the base, the other a breakpoint override. That's the normal responsive pattern, not an error.
Why D tempts people
The flex container is active, so it uses flex layout (a column at this width), not default block stacking.

45. Answer it before you see the options: Check: cascading breakpoints

Prediction

Predict first

A grid has className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3". How many columns show at 800px wide?

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: 2 columns — sm applies (≥640) but lg (≥1024) does not.

Why: Prefixes apply from their width upward until a larger one takes over. At 800px, sm (≥640) is active and lg (≥1024) is not yet, so sm:grid-cols-2 governs — two columns show.

46. Check: cascading breakpoints

Check

Apply the cascade rule to a specific width.

Check your understanding

A grid has className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3". How many columns show at 800px wide?

  • A. 1 column — the base rule always wins.
  • B. 2 columns — sm applies (≥640) but lg (≥1024) does not. (correct)
  • C. 3 columns — the largest listed rule wins.
  • D. It errors — you can't stack three column rules.

Answer: B

Why: Prefixes apply from their width upward until a larger one takes over. At 800px, sm (≥640) is active and lg (≥1024) is not yet, so sm:grid-cols-2 governs — two columns show.

Why A tempts people
The base rule is overridden once a matching prefix kicks in; at 800px sm:grid-cols-2 is active, so it's not 1 column.
Why C tempts people
lg only activates at 1024px and up; 800px is below that, so grid-cols-3 is not yet in effect.
Why D tempts people
Stacking multiple breakpoint prefixes on one property is the intended, standard pattern — not an error.

47. Building the Usual Suspects

Section

Section 3

48. Five blocks every landing page has

Concept

Almost every marketing page is the same five blocks in the same order: navbar, hero, feature grid, pricing, footer. Learn the responsive move for each once and you can compose any landing page.

For each block we'll do a short worked example and read the responsive classes off it — the class string tells you the whole story of how it behaves from phone to desktop.

49. Teach it back: Five blocks every landing page has

Explain it

Discussion prompt

Explain Five blocks every landing page has 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:

Almost every marketing page is the same five blocks in the same order: navbar, hero, feature grid, pricing, footer. Learn the responsive move for each once and you can compose any landing page.

50. The container: consistent page gutters

Concept

Every block shares the same horizontal breathing room — the gutter between content and the screen edge. Rather than repeat max-w-* mx-auto px-4 on each block, wrap the page or each section in a container and let it center and cap the content.

In Tailwind, container mx-auto px-4 centers the content and applies a max-width that steps up per breakpoint — comfortable on a phone, capped on a huge monitor. In Figma this is a fixed-max-width Auto Layout frame with horizontal padding.

Set the gutter once and every block inherits it. Consistent gutters are half of what makes a page feel 'designed' rather than assembled.

51. By analogy: The container: consistent page gutters

Analogy

Discussion prompt

Explain The container: consistent page gutters 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:

Set the gutter once and every block inherits it. Consistent gutters are half of what makes a page feel 'designed' rather than assembled.

52. Navbar: hide links on mobile

Worked example

The navbar is a horizontal Auto Layout: logo pinned left, links in the middle, CTA pinned right. On mobile the links collapse into a menu button.

<nav className="flex items-center justify-between px-4 py-3">
  <Logo />
  <div className="hidden md:flex gap-6">
    <a>Work</a><a>About</a><a>Pricing</a>
  </div>
  <Button className="md:hidden">Menu</Button>
</nav>

hidden md:flex — the links are hidden by default (mobile) and appear as a flex row from md up. md:hidden — the Menu button shows on mobile and hides on desktop. They're mirror images.

elementclassphone (<768)desktop (≥768)
links rowhidden md:flexhiddenvisible row
Menu buttonmd:hiddenvisiblehidden

53. Where does each piece belong: Session 5: Composition — Responsive Layouts…

Sorting

Sort into buckets

These are the pieces of Session 5: Composition — Responsive Layouts, Blocks & Pages, out of order. Put each one back under the part of the lesson it belongs to.

Components → Blocks → Pages
The three levels of composition; shadcn 'blocks', applied in Figma; A page is a shelf of blocks
Responsive Design in Figma
Breakpoints: widths where the layout changes; Mobile-first: base is the small screen; Enhance upward, never patch downward
Building the Usual Suspects
Five blocks every landing page has; The container: consistent page gutters; Navbar: hide links on mobile
s1
Components → Blocks → Pages is where Session 5: Composition — Responsive Layouts, Blocks & Pages puts The three levels of composition, shadcn 'blocks', applied in Figma, A page is a shelf of blocks. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s2
Responsive Design in Figma is where Session 5: Composition — Responsive Layouts, Blocks & Pages puts Breakpoints: widths where the layout changes, Mobile-first: base is the small screen, Enhance upward, never patch downward. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s3
Building the Usual Suspects is where Session 5: Composition — Responsive Layouts, Blocks & Pages puts Five blocks every landing page has, The container: consistent page gutters, Navbar: hide links on mobile. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.

54. Hero: scale the type down on mobile

Worked example

The hero is a centered column — heading, subcopy, CTA — capped with a max-width so the copy doesn't sprawl. The headline steps down the type scale on smaller screens.

<section className="flex flex-col items-center text-center gap-4 max-w-2xl mx-auto px-4 py-16">
  <h1 className="text-4xl md:text-6xl font-semibold">Design that ships.</h1>
  <p className="text-base text-muted-foreground">Aster builds themed, coded sites.</p>
  <Button size="lg">Start a project</Button>
</section>

text-4xl md:text-6xl — 2.25rem on mobile, 3.75rem from md up. Both are real steps on the scale, not made-up sizes. max-w-2xl mx-auto keeps the column centered and readable.

screenactive heading sizerem
phone (<768)text-4xl2.25rem
desktop (≥768)text-6xl3.75rem

55. Feature grid: 1 → 3 columns

Worked example

Three feature Cards, each with an icon, title, and line of copy. The grid is the reflow you've already seen — the block just fills it with themed Cards.

<section className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-5xl mx-auto px-4 py-12">
  <FeatureCard title="Themed" />
  <FeatureCard title="Coded" />
  <FeatureCard title="Responsive" />
</section>

grid-cols-1 md:grid-cols-3 is the whole responsive story; gap-6 (24px) holds the rhythm at every width. In Figma: a wrapping horizontal Auto Layout, or a fixed-count grid frame.

56. Pricing: a row of cards that wraps

Worked example

Three plan Cards side by side on desktop; on a phone they stack. Here we use flex with wrap so the cards break naturally as space runs out.

<section className="flex flex-wrap justify-center gap-6 max-w-5xl mx-auto px-4 py-12">
  <PriceCard plan="Starter" className="w-full md:w-72" />
  <PriceCard plan="Studio" className="w-full md:w-72" />
  <PriceCard plan="Agency" className="w-full md:w-72" />
</section>

flex-wrap lets the row break onto new lines. Each card is w-full on mobile (one per line) and a fixed md:w-72 on desktop, so three fit across. In Figma: horizontal Auto Layout with wrap on.

screencard widthresult
phone (<768)w-fullone card per row, stacked
desktop (≥768)md:w-72three cards wrap into one row

57. Fill in: result for Pricing: a row of cards that wraps

Comparison

Comparison matrix

From Pricing: a row of cards that wraps: refill the result column from what you know. The rest of the table is as it appeared.

screencard widthresult
phone (<768)w-fullone card per row, stacked
desktop (≥768)md:w-72three cards wrap into one row

58. Something is wrong here: a wrapping row with no wrap enabled

Anomaly

Predict first

A student writes this, and it looks reasonable:

Gordon builds the pricing row in Figma as a horizontal Auto Layout, sizes the cards to Fill, but leaves wrap off.

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

Correct: With wrap off, the cards can never move to a second line — they just squeeze thinner and thinner as the frame narrows.

Turn wrap on in Auto Layout — the reflow is a property, not something width alone triggers.

Why: With wrap off, the cards can never move to a second line — they just squeeze thinner and thinner as the frame narrows.

59. Trap: a wrapping row with no wrap enabled

Trap

The trap

Gordon builds the pricing row in Figma as a horizontal Auto Layout, sizes the cards to Fill, but leaves wrap off.

Three Fill cards in a no-wrap horizontal Auto Layout

Why: With wrap off, the cards can never move to a second line — they just squeeze thinner and thinner as the frame narrows.

In code: flex with no flex-wrap

Why: On a phone the three cards cram into one line, each far too narrow to read. Nothing breaks onto a new row.

The fix

Turn wrap on in Auto Layout — the reflow is a property, not something width alone triggers.

Enable wrap on the horizontal Auto Layout

Why: Now, when a card can't fit, it moves to the next line — the row becomes a column on narrow screens.

In code: flex flex-wrap

Why: flex-wrap is what lets the line break; with w-full cards on mobile, each takes its own row cleanly.

60. Footer: multi-column that stacks

Worked example

The footer is several link columns plus legal text. On desktop the columns sit in a row; on mobile they stack into one column.

<footer className="flex flex-col md:flex-row justify-between gap-8 px-4 py-10 border-t">
  <FooterCol title="Product" />
  <FooterCol title="Company" />
  <FooterCol title="Legal" />
</footer>

flex-col md:flex-row — the classic direction swap: stacked on mobile, a row from md up. justify-between spreads the columns across the width on desktop.

61. All five blocks, one cheat sheet

Worked example

Every landing block reduces to one responsive move. Learn this table and you can read — or write — any of them at a glance.

blockresponsive movethe class
Navbarhide links, show menuhidden md:flex / md:hidden
Herostep type downtext-4xl md:text-6xl
Feature grid1 col → 3 colsgrid-cols-1 md:grid-cols-3
Pricingrow that wrapsflex flex-wrap
Footerstack → rowflex-col md:flex-row

Notice the pattern under the pattern: it's always direction, count, or visibility, gated by md:. Three levers cover the whole page.

62. How sure are you: Check: the navbar links

Commit first

Predict first

Which class string makes a links row hidden on phones and a visible flex row on desktop (≥768px)?

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

Correct: hidden md:flex

Why: hidden is the base (mobile) state, so the row is hidden on phones. md:flex overrides it from 768px up, showing the links as a flex row on desktop. That's exactly the desktop-links / mobile-menu pattern.

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.

63. Check: the navbar links

Check

Gordon wants the nav links visible on desktop, hidden on mobile, replaced by a menu button.

Check your understanding

Which class string makes a links row hidden on phones and a visible flex row on desktop (≥768px)?

  • A. flex md:hidden
  • B. hidden md:flex (correct)
  • C. hidden md:hidden
  • D. md:flex

Answer: B

Why: hidden is the base (mobile) state, so the row is hidden on phones. md:flex overrides it from 768px up, showing the links as a flex row on desktop. That's exactly the desktop-links / mobile-menu pattern.

Why A tempts people
This is backwards: flex shows the row on mobile and md:hidden hides it on desktop — the opposite of what he wants.
Why C tempts people
This hides the row at every width; there's no width where the links become visible.
Why D tempts people
With no base display, the element defaults to visible on mobile (its normal display), so the links would show on phones too.

64. Consistency Across Breakpoints

Section

Section 4

65. Don't invent a mobile size — step down the scale

Concept

The temptation on mobile is to nudge a value to a random number that 'looks right' — a text-[19px] heading, a gap-[13px]. Every off-scale value is a small debt that makes the page feel subtly inconsistent.

Instead, step down the same scale. A text-2xl heading becomes text-xl, not text-[1.15rem]. A gap-8 (32px) section becomes gap-6 (24px), never gap-7px. You're moving to the next rung, not building a new ladder.

This is why the token system pays off: mobile and desktop share one scale, so the page reads as one design at every width — just tighter on small screens.

66. One ladder, not two

Intuition

Picture your type sizes and spacing as a ladder with fixed rungs. Going responsive is stepping down a rung or two for mobile — not sawing a new custom rung into the middle. Every element still lands on the same shared ladder.

That's why a well-built responsive page looks like one design breathing in and out, not two designs bolted together: mobile and desktop are the same ladder viewed at two heights.

67. The 4px spacing system holds at every width

Concept

Spacing rides the same discipline. Tailwind's base unit is 0.25rem = 4px, so gap-2 = 8px, gap-4 = 16px, gap-6 = 24px, gap-8 = 32px. Every gap and padding on the page should be one of these steps — at mobile and at desktop.

When you tighten spacing for a phone, drop to a lower step on the same 4px system: gap-8 → gap-6, py-16 → py-8. Never reach for gap-[13px] — that's a rung that exists nowhere else on the page.

steppxtypical use
gap-28pxtight inline spacing
gap-416pxdefault block gap
gap-624pxbetween cards
gap-832pxbetween sections' inner parts

68. Stepping type and space down for mobile

Worked example

Take the hero heading and section padding. The mobile values are lower rungs of the same scale, chosen with a breakpoint prefix on the desktop step.

<h1 className="text-3xl md:text-5xl">Aster</h1>
<section className="py-8 md:py-16 gap-4 md:gap-8">

Base = mobile step (text-3xl, py-8, gap-4); md: = desktop step (text-5xl, py-16, gap-8). No custom brackets anywhere.

propertymobile (base)desktop (md:)
headingtext-3xl (1.875rem)text-5xl (3rem)
section paddingpy-8 (32px)py-16 (64px)
inner gapgap-4 (16px)gap-8 (32px)

69. Something is wrong here: designing only desktop, hoping mobile just works

Anomaly

Predict first

A student writes this, and it looks reasonable:

Gordon designs the whole Aster page at 1440px, ships it, and assumes the browser will 'figure out' mobile.

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

Correct: With no responsive classes, the desktop layout applies at every width.

Gordon designs the mobile frame first, then adds md: overrides for desktop — two frames, both intentional.

Why: With no responsive classes, the desktop layout applies at every width. The 3-across grid stays 3-across on a 375px phone.

70. Trap: designing only desktop, hoping mobile just works

Trap

The trap

Gordon designs the whole Aster page at 1440px, ships it, and assumes the browser will 'figure out' mobile.

Only a desktop frame exists; no md/base decisions made

Why: With no responsive classes, the desktop layout applies at every width. The 3-across grid stays 3-across on a 375px phone.

On a real phone

Why: Cramped columns, an overflowing hero, links spilling off the edge. 'Mobile' was never actually designed — it just inherited desktop.

The fix

Gordon designs the mobile frame first, then adds md: overrides for desktop — two frames, both intentional.

Sketch the 375px frame: everything stacked, base classes

Why: Mobile is the default the code actually ships, so it's the layout you should design first, not last.

Add md: overrides for the 1440px frame

Why: Desktop is the enhancement layered on top. Now both breakpoints are deliberate and verified.

71. Which of these survive contact with Session 5: Composition — Responsive Layouts…?

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 stops, each one leaning on the last — ending with the full Aster homepage at two breakpoints.; The whole trick is that a block is built from components that already read your theme. Retint one semantic token and every block retints at once — you never restyle the page.; Keep that in mind as you build: every block is yours to shape, and every block reads the same tokens, so the page stays one coherent design.
Breaks
Gordon builds the hero at a comfortable Fixed width of 1200px because that's how it looks on his monitor.; Gordon wants three columns on desktop and one on mobile, and writes the prefix on the mobile intention.
sound
These are stated as this lesson states them — each one survives the edge cases Session 5: Composition — Responsive Layouts, Blocks & Pages 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.

72. Answer it before you see the options: Check: keeping values on the scale

Prediction

Predict first

The desktop heading is text-2xl (1.5rem) and needs to be smaller on mobile. Best practice is:

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: text-xl md:text-2xl — step down to the next rung on the scale for mobile.

Why: Keep values on the type scale: step the base (mobile) down one rung to text-xl (1.25rem) and let md:text-2xl restore 1.5rem on desktop. Same scale, mobile-first — no one-off sizes to maintain.

73. Check: keeping values on the scale

Check

On mobile a text-2xl heading feels a touch large. What's the on-system fix?

Check your understanding

The desktop heading is text-2xl (1.5rem) and needs to be smaller on mobile. Best practice is:

  • A. text-[1.15rem] md:text-2xl — a custom mobile size that looks right.
  • B. text-xl md:text-2xl — step down to the next rung on the scale for mobile. (correct)
  • C. text-2xl everywhere — leave it; the browser will shrink it on small screens.
  • D. text-2xl md:text-xl — smaller on desktop, larger on mobile.

Answer: B

Why: Keep values on the type scale: step the base (mobile) down one rung to text-xl (1.25rem) and let md:text-2xl restore 1.5rem on desktop. Same scale, mobile-first — no one-off sizes to maintain.

Why A tempts people
text-[1.15rem] is an off-scale custom value; it breaks the shared type scale and creates a size no other element uses.
Why C tempts people
The browser does not auto-shrink text-2xl on small screens; without a base override it stays 1.5rem on mobile.
Why D tempts people
This is backwards — it makes desktop smaller and mobile larger, the opposite of stepping down for the small screen.

74. Build It: The Aster Homepage

Section

Section 5 · Your turn

75. The build: one page, two breakpoints

Concept

You'll compose the whole Aster homepage from your themed components at two breakpoints — desktop and mobile. Five milestones, one per block. For each: do the task, peek at the hint only if stuck, then check your responsive classes against the solution and the self-check table.

Golden rule for the build: base class = mobile, md: = desktop, every value on the scale. Say it before each block.

#blockresponsive move
1Navbarhidden md:flex links + md:hidden menu
2Herotext-4xl md:text-6xl, centered max-w
3Feature gridgrid-cols-1 md:grid-cols-3
4Pricingflex-wrap row of cards
5Footerflex-col md:flex-row

76. Break it if you can: The build: one page, two breakpoints

Counterexample

Discussion prompt

Golden rule for the build: base class = mobile, md: = desktop, every value on the scale. Say it before each block.

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.

77. Set up: two frames, mobile first

Concept

Before Milestone 1, create two frames: a 375px mobile frame and a 1440px desktop frame. Build each block in the mobile frame first, then widen your thinking to the desktop frame — mirroring how the base and md: classes work in code.

Give both frames a vertical Auto Layout so blocks stack automatically, and set a container-style horizontal padding (px-4) so every block shares the same gutter. Now you're building the same page at two widths, not two pages.

78. Milestone 1 — Navbar that collapses on mobile

Worked example

Your turn: build the navbar as a horizontal Auto Layout — logo left, links center, CTA right. The links must hide on mobile and a Menu button take their place.

Hint: put hidden md:flex on the links row, and md:hidden on the Menu button. Space the outer items with justify-between.

<nav className="flex items-center justify-between px-4 py-3">
  <Logo />
  <div className="hidden md:flex gap-6">
    <a>Work</a><a>About</a><a>Pricing</a>
  </div>
  <Button variant="outline" className="md:hidden">Menu</Button>
</nav>
elementclassself-check
links rowhidden md:flexhidden <768, row ≥768 ✓
Menu buttonmd:hiddenshown <768, hidden ≥768 ✓
outer layoutjustify-betweenlogo left, CTA right ✓

79. Milestone 2 — Hero that scales type down

Worked example

Your turn: build a centered hero — heading, subcopy, CTA — capped with a max-width. The heading should be smaller on mobile, larger on desktop, both on the scale.

Hint: text-4xl md:text-6xl on the h1; wrap the column in max-w-2xl mx-auto text-center.

<section className="flex flex-col items-center text-center gap-4 max-w-2xl mx-auto px-4 py-12 md:py-20">
  <h1 className="text-4xl md:text-6xl font-semibold">Design that ships.</h1>
  <p className="text-muted-foreground">Themed, coded sites — built by Aster.</p>
  <Button size="lg">Start a project</Button>
</section>
propertymobiledesktopself-check
headingtext-4xl (2.25rem)text-6xl (3.75rem)both on scale ✓
section paddingpy-12 (48px)py-20 (80px)stepped on scale ✓
widthmax-w-2xl mx-autosamecentered + capped ✓

80. Milestone 3 — Feature grid 1 → 3

Worked example

Your turn: lay out three feature Cards. One column on mobile, three across on desktop, with a consistent gap held at every width.

Hint: grid grid-cols-1 md:grid-cols-3 gap-6. Keep the gap unprefixed so it never changes.

<section className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-5xl mx-auto px-4 py-12">
  <FeatureCard title="Themed" />
  <FeatureCard title="Coded" />
  <FeatureCard title="Responsive" />
</section>
screencolumnsself-check
phone (<768)1grid-cols-1 base ✓
desktop (≥768)3md:grid-cols-3 override ✓
gap24px bothgap-6 unprefixed ✓

81. What each one costs: Milestone 3 — Feature grid 1 → 3

Trade off

Comparison matrix

From Milestone 3 — Feature grid 1 → 3: every row here is a choice with a cost. Fill the self-check column, then say which row you would actually pick and what you give up for it.

screencolumnsself-check
phone (<768)1grid-cols-1 base ✓
desktop (≥768)3md:grid-cols-3 override ✓
gap24px bothgap-6 unprefixed ✓

82. Milestone 4 — Pricing row that wraps

Worked example

Your turn: three plan Cards. Stacked on mobile, wrapping into one row on desktop. Each card full-width on mobile, a fixed width on desktop.

Hint: flex flex-wrap justify-center gap-6 on the section; w-full md:w-72 on each card. In Figma this is horizontal Auto Layout with wrap on.

<section className="flex flex-wrap justify-center gap-6 max-w-5xl mx-auto px-4 py-12">
  <PriceCard plan="Starter" className="w-full md:w-72" />
  <PriceCard plan="Studio" className="w-full md:w-72" />
  <PriceCard plan="Agency" className="w-full md:w-72" />
</section>
screencard widthself-check
phone (<768)w-fullone per row, stacked ✓
desktop (≥768)md:w-72three wrap into a row ✓
containerflex-wraprow can break ✓

83. Fill in: self-check for Milestone 4 — Pricing row that wraps

Comparison

Comparison matrix

From Milestone 4 — Pricing row that wraps: refill the self-check column from what you know. The rest of the table is as it appeared.

screencard widthself-check
phone (<768)w-fullone per row, stacked ✓
desktop (≥768)md:w-72three wrap into a row ✓
containerflex-wraprow can break ✓

84. Milestone 5 — Footer that stacks

Worked example

Your turn: a footer with three link columns plus a border on top. Stacked column on mobile, spread into a row on desktop.

Hint: flex flex-col md:flex-row justify-between gap-8 — the classic direction swap. Add border-t for the divider.

<footer className="flex flex-col md:flex-row justify-between gap-8 px-4 py-10 border-t">
  <FooterCol title="Product" />
  <FooterCol title="Company" />
  <FooterCol title="Legal" />
</footer>
screendirectionself-check
phone (<768)flex-colcolumns stacked ✓
desktop (≥768)md:flex-rowcolumns in a row ✓
spacinggap-8 (32px)on the scale ✓

85. Put it together: the whole page

Worked example

Stack all five blocks in the page's vertical Auto Layout. Each block owns its own responsive behaviour, so the page just lists them.

export default function Home() {
  return (
    <main className="flex flex-col min-h-screen">
      <Navbar />
      <Hero />
      <FeatureGrid />
      <Pricing />
      <Footer />
    </main>
  )
}

Resize the browser from 1440px down to 375px and watch: nav links fold into a menu, the hero shrinks, the grid goes 3 → 1, pricing stacks, the footer stacks. Nothing was positioned by hand — the classes did it.

block1440px (desktop)375px (phone)
Navbarlinks visiblemenu button
Herotext-6xltext-4xl
Feature grid3 columns1 column
Pricingrow of 3stacked
Footerrowstacked

86. Show it off: the two frames side by side

Worked example

Put your 375px and 1440px Figma frames next to each other. They should read as the same design at two sizes — same colors, same type scale, same spacing rhythm — not two different pages.

Figure (svg): Phone frame with stacked blocks beside a desktop frame with rows and a 3-column grid

If they look like siblings, you nailed it: you designed a complete, responsive page entirely from themed shadcn components — the goal of this session.

87. Homework: finish the page, self-check every value

Concept

Homework: finish any sections you sketched but didn't build out — a testimonials block, an FAQ, a secondary CTA — each as one more block slotted into the vertical stack, at both breakpoints.

Then audit every value against the token system: is each font size a real text-* step? Is every gap and padding on the 4px scale? Any custom text-[..] or w-[..]px you can pull back onto the scale? Any Fixed-width block that should be Fill + max-w?

Bring the two frames — 375px and 1440px — to next session. In Session 6 you'll take this exact page to code and handoff: npx shadcn@latest init, Dev Mode, and honest Figma-variable → Tailwind-class mapping.

88. Rule out three: Check: why the page 'just works' when resized

Elimination

Eliminate the wrong options

The single most important reason the Aster page reflows correctly across breakpoints is:

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. Each block uses Fill/max-w widths and breakpoint classes (grid-cols-1 md:grid-cols-3, flex-col md:flex-row) so reflow is built into the layout.
  • B. He set an absolute pixel position and width on every block to match the desktop mockup exactly.
  • C. The browser automatically shrinks any desktop layout to fit a phone with no responsive classes needed.
  • D. He built a completely separate second page just for mobile and swaps it in with JavaScript.

Survives elimination: A

Why: Reflow is built into each block: widths are Fill (w-full) capped with max-w rather than fixed, and breakpoint classes like grid-cols-1 md:grid-cols-3 and flex-col md:flex-row change layout at defined widths. The mobile-first base plus md: overrides make one page adapt itself.

89. Check: why the page 'just works' when resized

Check

Gordon's page reflows perfectly from 1440px to 375px without him repositioning anything. What made that possible?

Check your understanding

The single most important reason the Aster page reflows correctly across breakpoints is:

  • A. Each block uses Fill/max-w widths and breakpoint classes (grid-cols-1 md:grid-cols-3, flex-col md:flex-row) so reflow is built into the layout. (correct)
  • B. He set an absolute pixel position and width on every block to match the desktop mockup exactly.
  • C. The browser automatically shrinks any desktop layout to fit a phone with no responsive classes needed.
  • D. He built a completely separate second page just for mobile and swaps it in with JavaScript.

Answer: A

Why: Reflow is built into each block: widths are Fill (w-full) capped with max-w rather than fixed, and breakpoint classes like grid-cols-1 md:grid-cols-3 and flex-col md:flex-row change layout at defined widths. The mobile-first base plus md: overrides make one page adapt itself.

Why B tempts people
Absolute pixel positions and fixed widths are exactly what prevents reflow — they pin the layout and cause overflow on smaller screens.
Why C tempts people
Browsers do not auto-shrink a desktop layout; without responsive classes the desktop layout applies at every width and breaks on mobile.
Why D tempts people
A single responsive page is the whole point; maintaining a separate JS-swapped mobile page is the brittle approach this session avoids.

90. Connect it up: Session 5: Composition — Responsive Layouts, Blocks & Pages

Connect it up

Draw it

One page, no notation unless you need it: draw how these connect — Components → Blocks → Pages · Responsive Design in Figma · Building the Usual Suspects · Consistency Across Breakpoints · Build It: The Aster Homepage. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.

91. What you can do now

Recap

wantTailwind move
one col → threegrid-cols-1 md:grid-cols-3
stack → rowflex-col md:flex-row
hide on mobilehidden md:flex
fill but cappedw-full max-w-* mx-auto
row that wrapsflex flex-wrap

Next time (Session 6): take this exact responsive Aster page to code and handoff — npx shadcn@latest init, adding components, Dev Mode & Code Connect, and honest Figma-variable → Tailwind-class mapping.

Sources

  1. shadcn/ui — Blocks
  2. Tailwind CSS — Responsive Design (breakpoints, mobile-first)
  3. Tailwind CSS — Grid Template Columns & Flexbox
  4. Figma — Apply constraints to define how layers respond to resizing
  5. Figma — Explore auto layout properties (wrap, resizing)

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

Book on Wyzant · Text (657) 465-8108