Skip to content
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
320px
sm
375px
md
430px
lg
768px
xl
1024px
2xl
1440px
xs Small Phone · 320px+ · iPhone SE, Galaxy A13
Token Reference

Breakpoint Definitions

NameTokenValueLabelDevicesContainerGrid colsPage pad
xs← active--ds-breakpoint-xs320pxSmall PhoneiPhone SE, Galaxy A13100%112px
sm--ds-breakpoint-sm375pxStandard PhoneiPhone 14, Pixel 7100%116px
md--ds-breakpoint-md430pxLarge PhoneiPhone 14 Pro Max, Galaxy S23 Ultra100%220px
lg--ds-breakpoint-lg768pxTabletiPad Mini, Surface Go768px324px
xl--ds-breakpoint-xl1024pxDesktopiPad Pro, 13" laptop1024px432px
2xl--ds-breakpoint-2xl1440pxWideDesktop monitor, 1440p display1280px440px
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

BreakpointMin-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.