Design System / 06
Responsive
Mobile-first layouts switch structure at 768px while type and gutters scale continuously. Touch targets, safe areas, and the catalog PWA are documented here.
- base
- 0px
- Mobile-first defaults; compositions begin in one column.
- sm
- 640px
- Optional density step for local padding or small grids.
- md
- 768px
- Primary hinge: package compositions split and NavBar returns inline.
- lg
- 1024px
- Dense application layouts may widen; this catalog reveals its sidebar.
- xl
- 1280px
- Role clamps are at or near their maximum rem tier.
- 2xl
- 1536px
- No mandatory reflow; measures hold and whitespace absorbs width.
Fluid type
Each display or heading role uses one clamp(), while the root itself scales from 15 to 16.5px. The role endpoints are therefore rem values, not fixed pixel promises: even after a role reaches its maximum rem tier near 1280px, its resolved pixels can continue growing with the root. Body copy inherits that root and never drops below 15px. Resize the window to see the specimens scale.
Resolved catalog samples
CSS pixels from the shipped role clamps and root formula, rounded to two decimals. A host that overrides the root size will resolve different pixels while keeping the same rem contract.
| Viewport | Root / body | Display | Heading 1 |
|---|---|---|---|
| 320px | 15.00px | 41.25px | 30.00px |
| 390px | 15.00px | 44.31px | 31.33px |
| 768px | 15.30px | 67.04px | 41.62px |
| 1024px | 15.56px | 82.52px | 48.67px |
| 1536px | 16.10px | 96.58px | 56.34px |
/* The root and each role scale independently. */
html { font-size: clamp(15px, 14.5px + 0.104vw, 16.5px); }
--fluid-display: clamp(2.75rem, 1.42rem + 5.9vw, 6rem);
--fluid-h1: clamp(2rem, 1.41rem + 2.61vw, 3.5rem);
--fluid-h2: clamp(1.5rem, 1.21rem + 1.3vw, 2.25rem);
/* Use it — no breakpoints needed. */
h1 { font-size: var(--fluid-h1); }Technical detail
- Utility
.tap-targetfloors the hit area to a44 × 44CSS-pixel minimum at every pointer type, leaving the visual box unchanged.- Standard
- Meets Apple’s 44pt guidance and WCAG 2.5.5 Target Size (Enhanced, Level AAA), and exceeds WCAG 2.5.8’s
24pxLevel AA minimum. - Use for
- Compact menu buttons, icon controls, and carousel dots.
--tap-min: 44px;
.tap-target { /* floor the hit area, keep the visual box */
min-height: var(--tap-min);
min-width: var(--tap-min);
}Technical detail
- Insets
- Directional utilities set an edge to
env(safe-area-inset-*); they replace, not add to, existing padding — usemax()orcalc()when a design gap must remain. - Gap floor
.pb-safe-gaptakes the larger of a bottom gap and the inset.- Viewport
.min-h-dvhtracks visible browser chrome withvhandsvhfallbacks.
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
/* utilities */
.pt-safe { padding-top: env(safe-area-inset-top); }
.pb-safe { padding-bottom: env(safe-area-inset-bottom); }
.pl-safe { padding-left: env(safe-area-inset-left); }
.pr-safe { padding-right: env(safe-area-inset-right); }
.px-safe { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
.inset-safe { padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); }
.pb-safe-gap { padding-bottom: max(var(--bar-gap, 0px), env(safe-area-inset-bottom)); }
.touch-scroll { -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
.min-h-dvh { min-height: 100vh; min-height: 100svh; min-height: 100dvh; }Technical detail
- Motion
- The label settles over
380msand retreats in220ms; width still changes discretely. - Color
- Inherits
currentColor, so it reads ink on paper and paper on ink. - Built on
- This site’s own
Logoand theNavBarbrand.
First: collapses past 32px, then re-expands on hover or focus. Second: static via collapse=false, with a non-string label and explicit ariaLabel.
<Wordmark href="/" mark={<YourMark />} label="YOUR MARK" />
// static (no scroll collapse):
<Wordmark mark={<YourMark />} label="YOUR MARK" collapse={false} />Installable PWA
The deployed catalog is installable and offline-capable: its manifest declares a standalone identity and V// icons, while a dependency-free service worker handles navigations network-first, static assets stale-while-revalidate, and an offline route when no page copy exists. The excerpts below show the Next static-export boundary.
Manifest and browser-chrome colors must serialize as literal color values; CSS custom properties are not valid there. This is a platform metadata exception only — keep the values synchronized with the host palette and never copy literals into component or utility styles.
import type { MetadataRoute } from "next";
export const dynamic = "force-static";
// app/manifest.ts → /manifest.webmanifest (install fields)
export default function manifest(): MetadataRoute.Manifest {
return {
name: "vvver design system",
short_name: "vvver DS",
id: "/",
start_url: "/",
scope: "/",
display: "standalone",
background_color: "#fdfcfb",
theme_color: "#fdfcfb",
icons: [
{ src: "/icon-192.png", sizes: "192x192", type: "image/png", purpose: "any" },
{ src: "/icon-512.png", sizes: "512x512", type: "image/png", purpose: "any" },
{ src: "/icon-maskable-192.png", sizes: "192x192", type: "image/png", purpose: "maskable" },
{ src: "/icon-maskable-512.png", sizes: "512x512", type: "image/png", purpose: "maskable" },
],
};
}import type { Metadata, Viewport } from "next";
// app/layout.tsx — declare the install identity + safe-area opt-in
export const viewport: Viewport = {
viewportFit: "cover",
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#fdfcfb" },
{ media: "(prefers-color-scheme: dark)", color: "#0b0a09" },
],
};
export const metadata: Metadata = {
manifest: "/manifest.webmanifest",
appleWebApp: { capable: true, statusBarStyle: "default", title: "vvver DS" },
icons: { apple: "/apple-touch-icon.png" },
};A small client registers the worker (production only, so next dev never caches), and a versioned cache name drops stale assets on activate. Cross-origin and non-GET requests pass through without service-worker interception.
Write a complete source order at the base tier; add the md split only when the wider composition needs it.
Use the fluid role tokens instead of duplicating font-size breakpoints; remember that the host root determines resolved pixels.
Choose media crop and full bleed per image, not as automatic phone behavior; check the subject at every crop.
Hold prose and section measures on wide screens; do not fill unused width with incidental columns.
Apply the 44px target floor to every custom control, especially menu buttons, icon actions, and carousel dots.
Use --gutter-fluid for the 1.25–2.5rem page rhythm; the host root determines its pixels as the viewport changes.
Keep text-entry controls at 16px or larger to avoid iOS focus zoom, independent of the smaller fluid root.
Pad every edge-pinned surface for safe areas and use the packaged vh → svh → dvh fallback for viewport-height shells.