Responsive Intelligence
See it work
Pick a width or drag the corner. Watch the sidebar become a drawer, the logo and breadcrumb swap, the cards reflow and the heading shrink. None of it uses a breakpoint class.
Good morning, Jordan
Revenue
1,284
Active users
1,284
Conversion
1,284
Churn
1,284
Four layers
Layer 1
The shell follows its frame
AppFrame measures its own width, not the window's. Below 768px (or 1024px with compactBelow="lg") the one Sidebar slides in as a drawer and SidebarTrigger appears. It behaves the same on a phone, in a narrow window and inside a Studio preview.
Layer 2
Content follows its container
Grid columns="auto" fits as many columns as the space allows, each at least minWidth wide, up to maxColumns. Cards reflow when the sidebar opens, collapses or becomes a drawer, with no breakpoint classes.
Layer 3
Details follow the frame too
shavin-frame-compact-only and shavin-frame-wide-only show or hide a detail (a logo in the top bar, a breadcrumb parent) in step with the drawer. Pure CSS, so nothing flashes on first paint.
Layer 4
Headings scale smoothly
Heading sizes xl and up grow with the nearest container, or the window outside one, between a fixed floor and ceiling. A hero is 72px on a wide page and shrinks in a narrow column or panel on its own.
The shell: AppFrame
Write the navigation once and pass it as sidebar. Put SidebarTrigger in the TopBar; it only shows while the frame is compact. Pass routeKey so the drawer closes after navigation.
<AppFrame routeKey={pathname} sidebar={<Sidebar>…</Sidebar>}>
<TopBar>
<SidebarTrigger />
<Logo className="shavin-frame-compact-only" />
<Breadcrumbs className="shavin-frame-wide-only" />
</TopBar>
<main>…</main>
</AppFrame>Content: Grid columns="auto"
minWidth sets the narrowest a card may get (xs 10rem, sm 14rem, md 18rem, lg 24rem, xl 32rem); maxColumns stops a row from spreading too wide. Keep fixed columns for splits, spans and timelines.
<Grid columns="auto" minWidth="sm" maxColumns="4" gap="4">
{stats.map((s) => <StatCard key={s.id} {...s} />)}
</Grid>Type: fluid Heading sizes
Pick the size for the widest layout. On a phone, in a split column or inside a narrow app frame it scales down on its own. Body and UI text stay fixed for readability, and every size still grows with browser zoom.
| Size | Range | Typical use |
|---|---|---|
5xl | 36–72px | Hero |
4xl | 36–60px | Display |
3xl | 36–48px | Section title, CTA |
2xl | 30–36px | Section title |
xl | 24–30px | Sub-section, card group |
lg, md, sm | 24, 20, 18px (fixed) | Card and UI titles |
Need one size everywhere? A size class in className replaces the fluid size.
<Heading as="h1" size="5xl">Grows from 36 to 72px</Heading>
<Heading as="h1" size="5xl" className="text-6xl">Always 60px</Heading>Agent guard
validate_code, audit_source and check_page warn when code goes back to screen-width tricks, so agents keep to the patterns above.
| Rule | Catches |
|---|---|
viewport-sidebar | A Sidebar shown or hidden with md:hidden / hidden md:block |
drawer-nav-copy | A second copy of the navigation inside a Drawer |
matchmedia-shell | matchMedia width checks next to a Sidebar |
manual-menu-button | A hand-made menu IconButton instead of SidebarTrigger |
viewport-in-frame | md:hidden / hidden md:block inside an AppFrame |
viewport-heading-size | Heading sizes stepped with breakpoint size classes such as md:text-5xl |
When breakpoints are still right
Screen-width breakpoints stay for whole-page art direction that intrinsic layout cannot express: a hero that swaps its image for a different crop, a section that reorders its columns, a marketing header menu. Tables and list-and-detail layouts do not have an adaptive pattern yet.