Skip to content
VVVER

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.

Breakpoints

The package uses Tailwind’s default, min-width ladder. Author mobile behavior at the base tier, then use md (768px) for the system’s primary structural change. sm may adjust local density; lg is reserved for layouts that genuinely need more room, including this catalog’s sidebar.

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.

Display--fluid-display · 2.75 → 6rem
Heading 1--fluid-h1 · 2 → 3.5rem
Heading 2--fluid-h2 · 1.5 → 2.25rem
Heading 3--fluid-h3 · 1.15 → 1.4rem
Lede--fluid-lede · 1.05 → 1.2rem

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.

Resolved root, display, and heading one sizes at five viewport widths
ViewportRoot / bodyDisplayHeading 1
320px15.00px41.25px30.00px
390px15.00px44.31px31.33px
768px15.30px67.04px41.62px
1024px15.56px82.52px48.67px
1536px16.10px96.58px56.34px
css
/* 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); }

Touch targets

Interactive controls keep a reliable minimum hit area even when the drawn mark is smaller, so taps land on touch devices without enlarging the visual design.

Technical detail

Utility
.tap-target floors the hit area to a 44 × 44 CSS-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 24px Level AA minimum.
Use for
Compact menu buttons, icon controls, and carousel dots.
44 × 44px minimum target; the dashed mark may be smaller.
css
--tap-min: 44px;

.tap-target {          /* floor the hit area, keep the visual box */
  min-height: var(--tap-min);
  min-width: var(--tap-min);
}

Safe areas

With viewport-fit=cover, edge-to-edge layouts must pad around the notch and home indicator. The packaged utilities below set each edge to its safe-area inset, take the larger of a gap and the inset, and track visible browser chrome.

Technical detail

Insets
Directional utilities set an edge to env(safe-area-inset-*); they replace, not add to, existing padding — use max() or calc() when a design gap must remain.
Gap floor
.pb-safe-gap takes the larger of a bottom gap and the inset.
Viewport
.min-h-dvh tracks visible browser chrome with vh and svh fallbacks.
css
<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; }

Adaptive navigation

NavBar shows inline links on wide viewports and folds them behind a menu button on narrow ones, opening a full-screen ink curtain that rises from the bottom with a staggered masked reveal of large stacked links.

Technical detail

Breakpoint
Inline links on md+; folds behind a MENU button below md.
Behavior
Locks scroll, closes on Escape or link tap, goes inert while closed, pads the safe area, and marks the current page via each link’s active flag.
This catalog
The chapter menu follows the same curtain pattern but folds below lg (sidebar appears at 1024px); it is not the NavBar demo.

Resize under 768px — the links fold behind MENU into a full-screen overlay; the active link is marked by weight and a square/dot marker.

tsx
<NavBar
  brand={<Logo />}
  links={[
    { href: "/work", label: "Work", active: true },
    { href: "/studio", label: "Studio" },
    { href: "/contact", label: "Contact" },
  ]}
  cta={<Button href="/sign-in" size="sm">Sign in</Button>}
/>

Brand wordmark

Wordmark is the brand lockup with scroll-compact motion: the label collapses to just the mark once you scroll past a threshold and re-expands at the top or on hover and focus. Your mark stays yours — only the transform-and-opacity motion is shared.

Technical detail

Motion
The label settles over 380ms and retreats in 220ms; width still changes discretely.
Color
Inherits currentColor, so it reads ink on paper and paper on ink.
Built on
This site’s own Logo and the NavBar brand.
CATALOGCATALOG Reference

First: collapses past 32px, then re-expands on hover or focus. Second: static via collapse=false, with a non-string label and explicit ariaLabel.

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

app/manifest.tstsx
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" },
    ],
  };
}
app/layout.tsxtsx
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.

Field notes

Practical rules for keeping a monochrome editorial layout consistent across devices.

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