Adapting blocks and patterns for mobile
Four breakpoints, predictable changes, no per-page media queries. Once you know the rule for each block, the page falls out automatically.
Breakpoint conventions
Four named breakpoints, each with one job. Don't introduce new ones.
| Token | Width | What happens |
|---|---|---|
--bp-toc | 1280px | TOC sidebar hides. Article goes full width. The first thing to go on smaller laptops. |
--bp-nav | 1100px | Top nav collapses into the brand row. Search shrinks to an icon. |
--bp-drawer | 900px | Sidebar becomes a drawer behind a menu toggle. Two-column layouts stack. |
--bp-phone | 720px | Full phone treatment — bottom nav, bottom sheets, card-list tables, segmented controls become selects. |
The pattern: each block declares its own rule at each breakpoint. The page never adds media queries — only blocks do. If your page needs a media query, the underlying block is missing one.
Block-by-block adaptation rules
What each block does at < 720px. The full responsive plan lives on each block's own doc; this is the cheat sheet.
| Block | Phone treatment |
|---|---|
| Page header | Title, lede, and actions stack vertically. Primary action moves below the lede; secondary actions collapse into a "⋯" menu. |
| Data toolbar | Search row on top. Filter chips become a horizontally scrolling row with momentum scroll — no wrapping. |
| Data table | Becomes a card list. Each row is a mobile list item with the most important 2–3 columns; the rest are revealed on tap. |
| Settings shell | 240px section rail becomes a top-level select with all section names; chosen section renders below. |
| Modal | Centered modal becomes a bottom sheet with a drag handle. Backdrop fades; sheet swipes to dismiss. |
| KPI grid | 4-up grid becomes 2-up at 900px and 1-up at phone. Cards keep their full content; no truncation. |
| Form shell | Sticky action bar becomes a bottom-anchored Mobile action bar with the primary action filling the width. |
| Detail hero | Two-column hero stacks. Primary action becomes a sticky bottom button. Secondary facts collapse to a "More" expander. |
Pattern-level rules
Patterns own larger transitions — whole navigation models change.
| Pattern | Phone treatment |
|---|---|
| App shell | Sidebar disappears entirely. Up to 5 top-level destinations live in a bottom nav (see portal-shell.css .ps-tabbar). |
| Detail + tabs | Tab strip becomes a segmented control if 2–4 tabs, or a select if 5+. Active tab body fills the rest of the screen. |
| Detail view | Two-column hero + summary rail becomes a single column with the summary collapsed below a "Details" header. |
| Command palette | Full-screen modal with on-screen keyboard awareness. ⌘K shortcut hidden; surface is reachable from a search icon in the app bar. |
| Bulk edit | Bulk action bar moves from sticky-top to sticky-bottom — thumb-reachable. |
| Import wizard | Steps collapse from horizontal stepper to a single "Step 2 of 4" indicator above the active step content. |
Before / after — four components
Each pair shows the desktop arrangement on the left and the phone treatment on the right.
1 · App shell → bottom nav
Desktop · ≥ 900px
Sidebar lists every destination. Topbar carries breadcrumb and global actions.
Phone · < 720px
Sidebar gone. Five top-level destinations sit in a thumb-reachable bottom nav.
2 · Data table → card list
Desktop · ≥ 900px
Full table with sortable columns, sticky header, and inline status.
Phone · < 720px
Each row becomes a card. Two most-important columns in title; the rest in the subtitle. Tap reveals more.
3 · Modal → bottom sheet
Desktop · ≥ 900px
Centered modal with backdrop. Focus trapped, Esc closes, return-focus on dismiss.
Phone · < 720px
Sheet rises from the bottom with a drag handle. Swipe-down dismisses. Primary action sits at thumb height.
4 · Settings rail → top select
Desktop · ≥ 900px
240px rail of sections on the left; scroll-spy highlights the active one.
Phone · < 720px
Rail collapses into a top-level select; chosen section renders below. No horizontal split on phone.
Bonus · Detail tabs → segmented control
Tab strips above 4 tabs become a select; 2–4 stay as segmented controls.
Desktop · ≥ 900px
Tabs are an underlined strip — full names visible.
Phone · < 720px
Strip becomes a segmented control. At 5+ tabs, it becomes a select instead.
"Test on a phone" checklist
Run through this before merging anything that touches a product surface.
- Open the page on a real phone (or DevTools at 375×812). Don't trust the responsive preview in your IDE.
- Every primary action is reachable with one thumb without scrolling.
- No horizontal scroll anywhere except the explicitly-scrollable filter chip row.
- Tap targets are at least 44×44px. Stack adjacent buttons rather than crowding them.
- Bottom nav is visible if the page is part of a portal. Page header doesn't hide behind it.
- Forms: the keyboard doesn't cover the active input. Sticky action bar sits above the keyboard.
- Modals are bottom sheets with a drag handle. Backdrop tap dismisses; Esc dismisses on hardware keyboard.
- Tables render as card lists. The "most important two" columns are in the card title; the rest are revealed on tap.
- Loading and empty states render correctly on phone too — check both.
- Pinch-zoom works (never set
user-scalable=no). Long-press shows the system context menu on text.
Do & don't
Put media queries inside blocks, never on pages. The page just composes blocks and inherits their responsive behavior.
Add a new breakpoint to fix one page. Adapt the misbehaving block at an existing breakpoint instead.
Test on a real phone before merging. Browser DevTools lie about touch behavior, sheet gestures, and on-screen keyboards.
Hide secondary actions behind a "⋯" menu on desktop. Reserve overflow menus for phone.
Make the primary action thumb-reachable on phone — bottom-anchored, full-width if necessary.
Disable pinch-zoom. Operators rely on it to read small numbers and codes; the system supports it everywhere.
What's next
For the full mobile component catalogue, see Mobile list, Mobile action bar, and the portal shells in portal-shell.css.