Foundation / Mobile
Responsive Breakpoint System
Six breakpoints cover the full device spectrum from small phones to wide monitors. The system is mobile-first: default styles target xs, and each breakpoint progressively enhances. Treasury dashboards adapt at three critical thresholds: lg (tablet, sidebar drawer), xl (desktop, fixed sidebar), and 2xl (wide, maximum data density).
Live Preview
Current Breakpoint
xs — Small Phone · 320px+ · iPhone SE, Galaxy A13
Token Reference
Breakpoint Definitions
| Name | Token | Value | Label | Devices | Container | Grid cols | Page pad |
|---|---|---|---|---|---|---|---|
| xs← active | --ds-breakpoint-xs | 320px | Small Phone | iPhone SE, Galaxy A13 | 100% | 1 | 12px |
| sm | --ds-breakpoint-sm | 375px | Standard Phone | iPhone 14, Pixel 7 | 100% | 1 | 16px |
| md | --ds-breakpoint-md | 430px | Large Phone | iPhone 14 Pro Max, Galaxy S23 Ultra | 100% | 2 | 20px |
| lg | --ds-breakpoint-lg | 768px | Tablet | iPad Mini, Surface Go | 768px | 3 | 24px |
| xl | --ds-breakpoint-xl | 1024px | Desktop | iPad Pro, 13" laptop | 1024px | 4 | 32px |
| 2xl | --ds-breakpoint-2xl | 1440px | Wide | Desktop monitor, 1440p display | 1280px | 4 | 40px |
Hook Spec
useBreakpoint()
Returns the current active breakpoint key as a reactive value. Re-renders the component when the viewport crosses a breakpoint boundary. Use this for layout decisions that cannot be expressed in CSS (conditional rendering, data fetch granularity, chart detail level).
import { useBreakpoint } from "@/lib/breakpoints";
import type { Breakpoint } from "@/lib/breakpoints";
// ─── Hook implementation ───────────────────────────────────────────
function useBreakpoint(): Breakpoint {
const [bp, setBp] = useState<Breakpoint>("xs");
useEffect(() => {
const getBreakpoint = (): Breakpoint => {
const w = window.innerWidth;
if (w >= 1440) return "2xl";
if (w >= 1024) return "xl";
if (w >= 768) return "lg";
if (w >= 430) return "md";
if (w >= 375) return "sm";
return "xs";
};
const handler = () => setBp(getBreakpoint());
setBp(getBreakpoint());
window.addEventListener("resize", handler);
return () => window.removeEventListener("resize", handler);
}, []);
return bp;
}
// ─── Usage examples ────────────────────────────────────────────────
function TreasuryDashboard() {
const bp = useBreakpoint();
// Conditional rendering based on breakpoint
const isMobile = ["xs", "sm", "md"].includes(bp);
const isTablet = bp === "lg";
const isDesktop = ["xl", "2xl"].includes(bp);
return (
<div>
{isMobile && <MobileNav />}
{isTablet && <TabNav />}
{isDesktop && <SidebarNav />}
{/* Layout adaptation */}
{isMobile
? <CardStackTable data={data} />
: <DataTable data={data} />
}
</div>
);
}
// ─── layoutAdaptations helper ──────────────────────────────────────
import { layoutAdaptations } from "@/lib/breakpoints";
function AdaptiveLayout() {
const bp = useBreakpoint();
const navMode = layoutAdaptations.navigation[bp];
const sidebarMode = layoutAdaptations.sidebar[bp];
const tableMode = layoutAdaptations.dataTable[bp];
return (
<AppShell
navigation={navMode} // "bottom-tab" | "top-tab" | "sidebar"
sidebar={sidebarMode} // "hidden" | "drawer" | "fixed"
table={tableMode} // "card-stack" | "compact-table" | "full-table"
/>
);
}Adaptive Layouts
Stack → Grid → Sidebar Transitions
xs → lg
Navigation
Bottom-tab → Top-tab
At 768px there's enough horizontal real estate for a persistent top navigation
--ds-breakpoint-lg
lg → xl
Navigation
Top-tab → Sidebar
At 1024px the sidebar no longer compresses content width below usable thresholds
--ds-breakpoint-xl
xs → lg
Data Table
Card stack → Compact table
Multi-column tables become readable at tablet widths; card-stack is the mobile fallback
--ds-breakpoint-lg
xs → lg
Agent Timeline
Vertical → Horizontal
Horizontal timeline needs 600px+ to show 4+ events without truncation
--ds-breakpoint-lg
lg → xl
Sidebar
Drawer → Fixed
At 1024px the sidebar is permanently visible; below it's a slide-in drawer
--ds-breakpoint-xl
xs → md
Grid Layout
1 column → 2 columns
At 430px (large phone landscape), two columns fit without cramping 160px cards
--ds-breakpoint-md
Current layout mode for viewport
sidebar
hidden
navigation
bottom-tab
dataTable
card-stack
agentTimeline
vertical
CSS Reference
Media Query Strings
| Breakpoint | Min-width (mobile-first) | Max-width (use sparingly) |
|---|---|---|
| xs | (min-width: 320px) | (max-width: 319px) |
| sm | (min-width: 375px) | (max-width: 374px) |
| md | (min-width: 430px) | (max-width: 429px) |
| lg | (min-width: 768px) | (max-width: 767px) |
| xl | (min-width: 1024px) | (max-width: 1023px) |
| 2xl | (min-width: 1440px) | (max-width: 1439px) |
Token Additions
semantic.css additions — sm and md breakpoints
The existing
tokens/semantic.css defines mobile (320px), tablet (768px), desktop (1024px), and wide (1440px). Add the following to fill the sm (375px) and md (430px) gaps for phone-specific breakpoints:/* Add to :root in tokens/semantic.css */
:root {
/* Existing breakpoints */
--ds-breakpoint-mobile: 320px; /* xs — Small phones */
--ds-breakpoint-tablet: 768px; /* lg — Tablets */
--ds-breakpoint-desktop: 1024px; /* xl — Desktop */
--ds-breakpoint-wide: 1440px; /* 2xl — Wide screens */
/* New — Sprint 24 additions */
--ds-breakpoint-xs: 320px; /* Small phones (alias for mobile) */
--ds-breakpoint-sm: 375px; /* Standard phones */
--ds-breakpoint-md: 430px; /* Large phones */
--ds-breakpoint-lg: 768px; /* Tablets (alias for tablet) */
--ds-breakpoint-xl: 1024px; /* Desktop (alias for desktop) */
--ds-breakpoint-2xl: 1440px; /* Wide (alias for wide) */
}
/* sm — Standard phone (375px+) */
@media (min-width: 375px) {
:root {
--ds-current-page-pad: var(--ds-page-pad-mobile); /* 16px */
}
}
/* md — Large phone (430px+) */
@media (min-width: 430px) {
:root {
--ds-current-grid-cols: 2; /* Two columns on large phones */
}
}Anti-Patterns
Breakpoint Misuse
Desktop-first overrides
Starting with desktop styles and overriding with max-width media queries. Results in specificity conflicts and hard-to-maintain override chains.
Always mobile-first. Write base styles for xs, then progressively add via min-width breakpoints. Never use max-width except for print.
Skipping sm and md
Jumping from xs (320px) directly to lg (768px). Large phones (375–430px) get xs layout which may be too compact, or lg layout which may have too much whitespace.
Use sm (375px) for font-size bumps and sm (430px) for 2-column grids. Treasury cards need the 430px breakpoint for proper grid layout.
Hardcoded px values in useBreakpoint logic
Component contains if (window.innerWidth > 768) — magic numbers not linked to the token system.
Import from lib/breakpoints.ts: if (window.innerWidth >= breakpoints.lg). Token system stays as single source of truth.
useBreakpoint in SSR context
Hook runs on the server where window is undefined, causing hydration mismatch or crash.
Initialize useState to 'xs' as the server default. The effect will correct to the actual breakpoint after hydration. Never access window outside useEffect.
Breakpoint-driven data fetching
Using the breakpoint to decide which API to call — chart detail level on mobile vs desktop. Causes waterfall fetches on resize.
Fetch the full dataset and filter/aggregate client-side based on breakpoint. Or use a stable, coarse breakpoint check (isMobile) that doesn't change on every resize.