Skip to main content
OrchestKit v6.7.1 — 67 skills, 38 agents, 77 hooks with Opus 4.6 support
OrchestKit
Skills

Responsive Patterns

Responsive design with Container Queries, fluid typography, cqi/cqb units, and mobile-first patterns for React applications. Use when building responsive layouts or container queries.

Reference medium

Primary Agent: frontend-ui-developer

Responsive Patterns

Modern responsive design patterns using Container Queries, fluid typography, and mobile-first strategies for React applications (2026 best practices).

Overview

  • Building reusable components that adapt to their container
  • Implementing fluid typography that scales smoothly
  • Creating responsive layouts without media query overload
  • Building design system components for multiple contexts
  • Optimizing for variable container sizes (sidebars, modals, grids)

Core Concepts

Container Queries vs Media Queries

FeatureMedia QueriesContainer Queries
Responds toViewport sizeContainer size
Component reuseContext-dependentTruly portable
Browser supportUniversalBaseline 2023+
Use casePage layoutsComponent layouts

CSS Patterns

See rules/css-patterns.md for complete CSS examples: container queries, cqi/cqb units, fluid typography with clamp(), mobile-first breakpoints, CSS Grid patterns, and scroll-queries.

Key patterns covered: Container Query basics, Container Query Units (cqi/cqb), Fluid Typography with clamp(), Container-Based Fluid Typography, Mobile-First Breakpoints, CSS Grid Responsive Patterns, Container Scroll-Queries (Chrome 126+).

React Patterns

See rules/react-patterns.md for complete React examples: ResponsiveCard component, Tailwind container queries, useContainerQuery hook, and responsive images.

Key patterns covered: Responsive Component with Container Queries, Tailwind CSS Container Queries, useContainerQuery Hook, Responsive Images Pattern.

Accessibility Considerations

/* IMPORTANT: Always include rem in fluid typography */
/* This ensures user font preferences are respected */

/* ❌ WRONG: Viewport-only ignores user preferences */
font-size: 5vw;

/* ✅ CORRECT: Include rem to respect user settings */
font-size: clamp(1rem, 0.5rem + 2vw, 2rem);

/* User zooming must still work */
@media (min-width: 768px) {
  /* Use em/rem, not px, for breakpoints in ideal world */
  /* (browsers still use px, but consider user zoom) */
}

Anti-Patterns (FORBIDDEN)

/* ❌ NEVER: Use only viewport units for text */
.title {
  font-size: 5vw; /* Ignores user font preferences! */
}

/* ❌ NEVER: Use cqw/cqh (use cqi/cqb instead) */
.card {
  padding: 5cqw; /* cqw = container width, not logical */
}
/* ✅ CORRECT: Use logical units */
.card {
  padding: 5cqi; /* Container inline = logical direction */
}

/* ❌ NEVER: Container queries without container-type */
@container (min-width: 400px) {
  /* Won't work without container-type on parent! */
}

/* ❌ NEVER: Desktop-first media queries */
.element {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}
@media (max-width: 768px) {
  .element {
    grid-template-columns: 1fr; /* Overriding = more CSS */
  }
}

/* ❌ NEVER: Fixed pixel breakpoints for text */
@media (min-width: 768px) {
  body { font-size: 18px; } /* Use rem! */
}

/* ❌ NEVER: Over-nesting container queries */
@container a {
  @container b {
    @container c {
      /* Too complex, reconsider architecture */
    }
  }
}

Browser Support

FeatureChromeSafariFirefoxEdge
Container Size Queries105+16+110+105+
Container Style Queries111+111+
Container Scroll-State126+126+
cqi/cqb units105+16+110+105+
clamp()79+13.1+75+79+
Subgrid117+16+71+117+

Rules

Each category has individual rule files in rules/ loaded on-demand:

CategoryRuleImpactKey Pattern
CSSrules/css-patterns.mdHIGHContainer queries, cqi/cqb, fluid typography, grid, scroll-queries
Reactrules/react-patterns.mdHIGHContainer query components, Tailwind, useContainerQuery, responsive images
PWArules/pwa-service-worker.mdHIGHWorkbox caching strategies, VitePWA, update management
PWArules/pwa-offline.mdHIGHOffline hooks, background sync, install prompts
Animationrules/animation-motion.mdHIGHMotion presets, AnimatePresence, View Transitions
Animationrules/animation-scroll.mdMEDIUMCSS scroll-driven animations, parallax, progressive enhancement

Total: 6 rules across 4 categories

Key Decisions

DecisionOption AOption BRecommendation
Query typeMedia queriesContainer queriesContainer for components, Media for layout
Container unitscqw/cqhcqi/cqbcqi/cqb (logical, i18n-ready)
Fluid type basevw onlyrem + vwrem + vw (accessibility)
Mobile-firstYesDesktop-firstMobile-first (less CSS, progressive)
Grid patternauto-fitauto-fillauto-fit for cards, auto-fill for icons
  • design-system-starter - Building responsive design systems
  • ork:performance - CLS, responsive images, and image optimization
  • ork:i18n-date-patterns - RTL/LTR responsive considerations

Capability Details

container-queries

Keywords: @container, container-type, inline-size, container-name Solves: Component-level responsive design

fluid-typography

Keywords: clamp(), fluid, vw, rem, scale, typography Solves: Smooth font scaling without breakpoints

responsive-images

Keywords: srcset, sizes, picture, art direction Solves: Responsive images for different viewports

mobile-first-strategy

Keywords: min-width, mobile, progressive, breakpoints Solves: Efficient responsive CSS architecture

grid-flexbox-patterns

Keywords: auto-fit, auto-fill, subgrid, minmax Solves: Responsive grid and flexbox layouts

container-units

Keywords: cqi, cqb, container width, container height Solves: Sizing relative to container dimensions

References

  • references/container-queries.md - Container query patterns
  • references/fluid-typography.md - Accessible fluid type scales
  • scripts/responsive-card.tsx - Responsive card component

Rules (6)

Use Motion library with centralized presets for 60fps animations and reduced-motion compliance — HIGH

Motion Library & View Transitions

Use Motion (Framer Motion) for React component animations and View Transitions API for page navigation with consistent presets.

Incorrect — inline animation values without presets:

// WRONG: Inconsistent animation values, no reduced-motion support
<motion.div
  initial={{ opacity: 0, y: 50, scale: 0.8 }}
  animate={{ opacity: 1, y: 0, scale: 1 }}
  transition={{ duration: 0.7, ease: "easeInOut" }}
>
  {/* Different values everywhere, no consistency */}
</motion.div>

Correct — centralized presets with AnimatePresence:

// lib/animations.ts — Single source of truth
export const fadeInUp = {
  initial: { opacity: 0, y: 20 },
  animate: { opacity: 1, y: 0 },
  exit: { opacity: 0, y: -10 },
  transition: { type: 'spring', stiffness: 300, damping: 30 },
};

export const staggerContainer = {
  animate: { transition: { staggerChildren: 0.05 } },
};

export const staggerItem = {
  initial: { opacity: 0, y: 10 },
  animate: { opacity: 1, y: 0 },
};

// Usage with AnimatePresence for exit animations
import { motion, AnimatePresence } from 'motion/react';
import { staggerContainer, staggerItem, fadeInUp } from '@/lib/animations';

function AnimatedList({ items }: { items: Item[] }) {
  return (
    <AnimatePresence mode="wait">
      <motion.ul variants={staggerContainer} initial="initial" animate="animate">
        {items.map((item) => (
          <motion.li key={item.id} variants={staggerItem} layout>
            {item.name}
          </motion.li>
        ))}
      </motion.ul>
    </AnimatePresence>
  );
}

View Transitions API for page navigation:

// React Router with View Transitions
import { Link, useViewTransitionState } from 'react-router';

function ProductCard({ product }: { product: Product }) {
  const isTransitioning = useViewTransitionState(`/products/${product.id}`);
  return (
    <Link to={`/products/${product.id}`} viewTransition>
      <img
        src={product.image}
        alt={product.name}
        style={{
          viewTransitionName: isTransitioning ? 'product-image' : undefined,
        }}
      />
    </Link>
  );
}

Key rules:

  • Animate only transform and opacity for 60fps (hardware accelerated)
  • Use centralized preset files, never inline animation values
  • Always wrap conditional renders in AnimatePresence for exit animations
  • Respect prefers-reduced-motion: reduce — disable or shorten animations
  • Keep transitions under 400ms to avoid blocking user interaction
  • Use View Transitions API for page navigation, Motion for component animations

Use CSS scroll-driven animations on the compositor thread for guaranteed 60fps performance — MEDIUM

Scroll-Driven Animations

Use CSS Scroll-Driven Animations API for performant, declarative scroll-linked effects without JavaScript.

Incorrect — JavaScript scroll listener (jank-prone):

// WRONG: Scroll listeners run on main thread, cause jank
window.addEventListener('scroll', () => {
  const progress = window.scrollY / document.body.scrollHeight;
  progressBar.style.width = `${progress * 100}%`; // Layout thrashing!
  parallaxElement.style.transform = `translateY(${window.scrollY * 0.5}px)`;
});

Correct — CSS Scroll-Driven Animations (compositor thread):

/* Reading progress bar — zero JavaScript */
.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 4px;
  background: var(--color-primary);
  transform-origin: left;
  animation: grow-progress linear;
  animation-timeline: scroll(root block);
}

@keyframes grow-progress {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

/* Reveal on scroll — element enters viewport */
.reveal-on-scroll {
  animation: fade-in-up linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

@keyframes fade-in-up {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Parallax section */
.parallax-bg {
  animation: parallax-shift linear;
  animation-timeline: scroll(root block);
}

@keyframes parallax-shift {
  from { transform: translateY(-20%); }
  to { transform: translateY(20%); }
}

Progressive enhancement (required for browser support):

/* Feature detection — fallback for unsupported browsers */
@supports (animation-timeline: view()) {
  .reveal-on-scroll {
    animation: fade-in-up linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 100%;
  }
}

/* Fallback: IntersectionObserver in JavaScript */
@supports not (animation-timeline: view()) {
  .reveal-on-scroll {
    opacity: 0;
    transform: translateY(30px);
    transition: opacity 0.5s, transform 0.5s;
  }
  .reveal-on-scroll.visible {
    opacity: 1;
    transform: translateY(0);
  }
}

Browser support:

FeatureChromeSafariFirefoxEdge
Scroll-Driven CSS115+18.4+In dev115+
scroll() function115+18.4+In dev115+
view() function115+18.4+In dev115+

Key rules:

  • CSS Scroll-Driven Animations run on compositor thread = guaranteed 60fps
  • Always use @supports (animation-timeline: view()) for progressive enhancement
  • Use scroll(root block) for page-level progress, view() for element visibility
  • animation-range controls when animation starts/ends relative to viewport
  • Never use JavaScript scroll listeners for visual effects (use CSS or IntersectionObserver)
  • Respect prefers-reduced-motion — disable parallax and motion effects

CSS Responsive Patterns — HIGH

CSS Responsive Patterns

1. Container Query Basics

/* Define a query container */
.card-container {
  container-type: inline-size;
  container-name: card;
}

/* Style based on container width */
@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
  }
}

@container card (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }
}

2. Container Query Units (cqi, cqb)

/* Use cqi (container query inline) over cqw */
.card-title {
  /* 5% of container's inline size */
  font-size: clamp(1rem, 5cqi, 2rem);
}

.card-content {
  /* Responsive padding based on container */
  padding: 2cqi;
}

/* cqb for block dimension (height-aware containers) */
.sidebar-item {
  height: 10cqb;
}

3. Fluid Typography with clamp()

/* Accessible fluid typography */
:root {
  /* Base font respects user preferences (rem) */
  --font-size-base: 1rem;

  /* Fluid scale with min/max bounds */
  --font-size-sm: clamp(0.875rem, 0.8rem + 0.25vw, 1rem);
  --font-size-md: clamp(1rem, 0.9rem + 0.5vw, 1.25rem);
  --font-size-lg: clamp(1.25rem, 1rem + 1vw, 2rem);
  --font-size-xl: clamp(1.5rem, 1rem + 2vw, 3rem);
  --font-size-2xl: clamp(2rem, 1rem + 3vw, 4rem);
}

h1 { font-size: var(--font-size-2xl); }
h2 { font-size: var(--font-size-xl); }
h3 { font-size: var(--font-size-lg); }
p { font-size: var(--font-size-md); }
small { font-size: var(--font-size-sm); }

4. Container-Based Fluid Typography

/* For component-scoped fluid text */
.widget {
  container-type: inline-size;
}

.widget-title {
  /* Fluid within container, respecting user rem */
  font-size: clamp(1rem, 0.5rem + 5cqi, 2rem);
}

.widget-body {
  font-size: clamp(0.875rem, 0.5rem + 3cqi, 1.125rem);
}

5. Mobile-First Breakpoints

/* Mobile-first: start small, add complexity */
.layout {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* Tablet and up */
@media (min-width: 768px) {
  .layout {
    flex-direction: row;
  }
}

/* Desktop */
@media (min-width: 1024px) {
  .layout {
    max-width: 1200px;
    margin-inline: auto;
  }
}

6. CSS Grid Responsive Patterns

/* Auto-fit grid (fills available space) */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}

/* Auto-fill grid (maintains minimum columns) */
.icon-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
  gap: 1rem;
}

/* Subgrid for nested alignment */
.card {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
}

7. Container Scroll-Queries (Chrome 126+)

/* Query based on scroll state */
.scroll-container {
  container-type: scroll-state;
  container-name: scroller;
}

@container scroller scroll-state(scrollable: top) {
  .scroll-indicator-top {
    opacity: 0;
  }
}

@container scroller scroll-state(scrollable: bottom) {
  .scroll-indicator-bottom {
    opacity: 0;
  }
}

Configure PWA offline support, manifest, and background sync for native-like installability — HIGH

PWA Offline & Installability

Implement offline-first patterns, background sync, install prompts, and web app manifests for native-like PWA experiences.

Incorrect — no offline fallback or install handling:

// WRONG: App crashes when offline
// No service worker fallback, no install prompt handling
// Users get browser error page instead of offline experience

Correct — offline status hook and install prompt:

// useOnlineStatus hook
export function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return isOnline;
}

// Install prompt hook
interface BeforeInstallPromptEvent extends Event {
  prompt: () => Promise<void>;
  userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
}

export function useInstallPrompt() {
  const [installPrompt, setInstallPrompt] = useState<BeforeInstallPromptEvent | null>(null);
  const [isInstalled, setIsInstalled] = useState(false);

  useEffect(() => {
    const handler = (e: BeforeInstallPromptEvent) => {
      e.preventDefault();
      setInstallPrompt(e);
    };
    window.addEventListener('beforeinstallprompt', handler as EventListener);
    if (window.matchMedia('(display-mode: standalone)').matches) setIsInstalled(true);
    return () => window.removeEventListener('beforeinstallprompt', handler as EventListener);
  }, []);

  const promptInstall = async () => {
    if (!installPrompt) return false;
    await installPrompt.prompt();
    const { outcome } = await installPrompt.userChoice;
    setInstallPrompt(null);
    if (outcome === 'accepted') { setIsInstalled(true); return true; }
    return false;
  };

  return { canInstall: !!installPrompt, isInstalled, promptInstall };
}

Background sync for offline form submissions:

// sw.js
import { BackgroundSyncPlugin } from 'workbox-background-sync';
import { registerRoute } from 'workbox-routing';
import { NetworkOnly } from 'workbox-strategies';

registerRoute(
  /\/api\/forms/,
  new NetworkOnly({
    plugins: [
      new BackgroundSyncPlugin('formQueue', {
        maxRetentionTime: 24 * 60, // 24 hours
      }),
    ],
  }),
  'POST'
);

PWA checklist:

  • Service worker registered with offline fallback
  • Web App Manifest with icons (192px + 512px maskable)
  • HTTPS enabled (required for service workers)
  • Offline page/experience works gracefully
  • Responsive design (meta viewport set)
  • Fast First Contentful Paint (< 1.8s)

Key rules:

  • Use display: "standalone" in manifest for app-like experience
  • Include both 192px and 512px icons with maskable purpose
  • Use BackgroundSyncPlugin for offline form submissions
  • Prompt install at a natural moment (not on page load)
  • Test offline behavior in Chrome DevTools Application tab

Configure service workers with Workbox 7.x caching strategies to prevent stale content — HIGH

PWA Service Worker & Workbox

Configure service workers with Workbox 7.x for reliable caching, update management, and installability.

Incorrect — service worker without update strategy:

// WRONG: No skipWaiting means users stuck on old version
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1').then((cache) =>
      cache.addAll(['/index.html', '/app.js', '/style.css'])
    )
  );
});
// Missing: skipWaiting, clientsClaim, no update prompt

Correct — Workbox with proper caching strategies:

import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

// Static assets: CacheFirst (hashed filenames = safe to cache long)
registerRoute(
  /\.(?:js|css|woff2)$/,
  new CacheFirst({
    cacheName: 'static-v1',
    plugins: [
      new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 365 * 24 * 60 * 60 }),
    ],
  })
);

// API calls: NetworkFirst (fresh data preferred, cached fallback)
registerRoute(
  /\/api\//,
  new NetworkFirst({
    cacheName: 'api-cache',
    networkTimeoutSeconds: 10,
    plugins: [new CacheableResponsePlugin({ statuses: [0, 200] })],
  })
);

// Avatars/images: StaleWhileRevalidate (show cached, update in background)
registerRoute(
  /\/avatars\//,
  new StaleWhileRevalidate({ cacheName: 'avatars' })
);

VitePWA integration:

// vite.config.ts
import { VitePWA } from 'vite-plugin-pwa';

export default defineConfig({
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
        runtimeCaching: [
          { urlPattern: /^https:\/\/api\./, handler: 'NetworkFirst' },
        ],
      },
      manifest: {
        name: 'My PWA App',
        short_name: 'MyPWA',
        theme_color: '#4f46e5',
        icons: [
          { src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
          { src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
        ],
      },
    }),
  ],
});

Key rules:

  • Use generateSW for simple apps, injectManifest for custom service worker logic
  • Always enable clientsClaim and skipWaiting for immediate activation
  • CacheFirst for static assets, NetworkFirst for APIs, StaleWhileRevalidate for non-critical images
  • Never cache POST responses or authentication tokens
  • Include navigateFallback: '/index.html' for SPA offline support

React Responsive Patterns — HIGH

React Responsive Patterns

Responsive Component with Container Queries

import { cn } from '@/lib/utils';

interface CardProps {
  title: string;
  description: string;
  image: string;
}

export function ResponsiveCard({ title, description, image }: CardProps) {
  return (
    <div className="@container">
      <article className={cn(
        "flex flex-col gap-4",
        "@md:flex-row @md:gap-6" // Container query breakpoints
      )}>
        <img
          src={image}
          alt=""
          className="w-full @md:w-48 aspect-video object-cover rounded-lg"
        />
        <div className="flex flex-col gap-2">
          <h3 className="text-[clamp(1rem,0.5rem+3cqi,1.5rem)] font-semibold">
            {title}
          </h3>
          <p className="text-[clamp(0.875rem,0.5rem+2cqi,1rem)] text-muted-foreground">
            {description}
          </p>
        </div>
      </article>
    </div>
  );
}

Tailwind CSS Container Queries

// @container enables container query variants (@sm, @md, @lg, etc.)
<div className="@container">
  <div className="flex flex-col @lg:flex-row @xl:gap-8">
    <div className="@sm:p-4 @md:p-6 @lg:p-8">
      Content adapts to container
    </div>
  </div>
</div>

useContainerQuery Hook

import { useRef, useState, useEffect } from 'react';

function useContainerQuery(breakpoint: number) {
  const ref = useRef<HTMLDivElement>(null);
  const [isAbove, setIsAbove] = useState(false);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const observer = new ResizeObserver(([entry]) => {
      setIsAbove(entry.contentRect.width >= breakpoint);
    });

    observer.observe(element);
    return () => observer.disconnect();
  }, [breakpoint]);

  return [ref, isAbove] as const;
}

// Usage
function AdaptiveCard() {
  const [containerRef, isWide] = useContainerQuery(400);

  return (
    <div ref={containerRef}>
      {isWide ? <HorizontalLayout /> : <VerticalLayout />}
    </div>
  );
}

Responsive Images Pattern

function ResponsiveImage({
  src,
  alt,
  sizes = "(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
}: {
  src: string;
  alt: string;
  sizes?: string;
}) {
  return (
    <picture>
      {/* Art direction with different crops */}
      <source
        media="(max-width: 640px)"
        srcSet={`${src}?w=640&aspect=1:1`}
      />
      <source
        media="(max-width: 1024px)"
        srcSet={`${src}?w=800&aspect=4:3`}
      />
      <img
        src={`${src}?w=1200`}
        alt={alt}
        sizes={sizes}
        loading="lazy"
        decoding="async"
        className="w-full h-auto object-cover"
      />
    </picture>
  );
}

References (2)

Container Queries

Container Queries Reference

Container Types

/* Size queries (width/height) */
container-type: inline-size;  /* Query inline dimension */
container-type: size;         /* Query both dimensions */
container-type: normal;       /* No containment (default) */

/* Named container */
container-name: card;

/* Shorthand */
container: card / inline-size;

Query Syntax

/* Width queries */
@container (min-width: 400px) { }
@container (max-width: 399px) { }
@container (width > 400px) { }
@container (400px <= width <= 800px) { }

/* Named container queries */
@container card (min-width: 400px) { }

/* Logical properties */
@container (min-inline-size: 400px) { }
@container (min-block-size: 300px) { }

Container Query Units

/* Inline dimension (usually width) */
cqi  /* 1% of container inline size */

/* Block dimension (usually height) */
cqb  /* 1% of container block size */

/* Min/max of cqi and cqb */
cqmin
cqmax

/* Legacy (avoid - not logical) */
cqw  /* Container width */
cqh  /* Container height */

Tailwind CSS v4 Integration

Container queries are built into Tailwind CSS v4 - no plugin required.

<!-- Enable container with @container -->
<div class="@container">
  <div class="flex flex-col @md:flex-row @lg:gap-8">
    <!-- Responsive to container, not viewport -->
  </div>
</div>

<!-- Named containers -->
<div class="@container/card">
  <div class="@lg/card:grid-cols-2">
    <!-- Queries the 'card' container -->
  </div>
</div>
/* app.css - Tailwind v4 CSS-first approach */
@import "tailwindcss";

@theme {
  /* Custom container breakpoints (optional) */
  --container-3xs: 16rem;
  --container-2xs: 18rem;
  --container-xs: 20rem;
  /* Default breakpoints: @sm (20rem), @md (28rem), @lg (32rem), etc. */
}

Best Practices

/* ✅ Use logical units */
.card-title {
  font-size: clamp(1rem, 5cqi, 2rem);
  padding: 2cqi;
}

/* ✅ Nest containers carefully */
.outer {
  container: outer / inline-size;
}
.inner {
  container: inner / inline-size;
}
@container inner (min-width: 200px) { }

/* ❌ Don't query non-container */
.no-container {
  /* No container-type set */
}
@container (min-width: 400px) {
  /* This won't work! */
}

Feature Detection

@supports (container-type: inline-size) {
  .card-container {
    container-type: inline-size;
  }
}

Fluid Typography

Fluid Typography Reference

The clamp() Formula

/* Syntax: clamp(min, preferred, max) */
font-size: clamp(1rem, 0.5rem + 2vw, 2rem);

/* Breakdown:
   min: 1rem (16px at default)
   preferred: 0.5rem + 2vw (scales with viewport)
   max: 2rem (32px at default)
*/

Accessible Fluid Scale

:root {
  /* Always include rem to respect user preferences */
  --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
  --text-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem);
  --text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
  --text-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem);
  --text-xl: clamp(1.25rem, 1rem + 1.25vw, 1.75rem);
  --text-2xl: clamp(1.5rem, 1rem + 2.5vw, 2.5rem);
  --text-3xl: clamp(1.875rem, 1rem + 4.375vw, 3.5rem);
  --text-4xl: clamp(2.25rem, 1rem + 6.25vw, 4.5rem);
}

h1 { font-size: var(--text-4xl); }
h2 { font-size: var(--text-3xl); }
h3 { font-size: var(--text-2xl); }
h4 { font-size: var(--text-xl); }
p { font-size: var(--text-base); }
small { font-size: var(--text-sm); }

Container-Based Fluid Type

/* For component-scoped scaling */
.card {
  container-type: inline-size;
}

.card-title {
  /* Scales with card width, not viewport */
  font-size: clamp(1rem, 0.5rem + 5cqi, 1.75rem);
}

.card-body {
  font-size: clamp(0.875rem, 0.5rem + 3cqi, 1rem);
}

Calculating Fluid Values

Target: 16px at 320px viewport → 24px at 1200px viewport

Formula:
preferred = min + (max - min) × (viewport - min-viewport) / (max-viewport - min-viewport)

Step 1: Convert to vw
  (24 - 16) / (1200 - 320) = 8 / 880 = 0.909% per px
  0.909 × 100 = 0.909vw

Step 2: Calculate rem offset
  At 320px: 16px = 1rem
  16 - (320 × 0.00909) = 16 - 2.91 = 13.09px ≈ 0.818rem

Result:
font-size: clamp(1rem, 0.818rem + 0.909vw, 1.5rem);

Accessibility Considerations

/* ❌ WRONG: Ignores user font preferences */
font-size: 5vw;

/* ❌ WRONG: Completely overrides user settings */
font-size: 16px;

/* ✅ CORRECT: Respects user preferences while scaling */
font-size: clamp(1rem, 0.5rem + 2vw, 2rem);

/* The rem portion ensures user's font-size preference
   is always a factor in the final size */

Line Height Scaling

/* Tighter line-height for larger text */
h1 {
  font-size: clamp(2rem, 1rem + 5vw, 4rem);
  line-height: clamp(1.1, 1.4 - 0.2vw, 1.3);
}

p {
  font-size: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
  line-height: 1.6; /* Static is fine for body */
}

Tools

Edit on GitHub

Last updated on

On this page

Responsive PatternsOverviewCore ConceptsContainer Queries vs Media QueriesCSS PatternsReact PatternsAccessibility ConsiderationsAnti-Patterns (FORBIDDEN)Browser SupportRulesKey DecisionsRelated SkillsCapability Detailscontainer-queriesfluid-typographyresponsive-imagesmobile-first-strategygrid-flexbox-patternscontainer-unitsReferencesRules (6)Use Motion library with centralized presets for 60fps animations and reduced-motion compliance — HIGHMotion Library & View TransitionsUse CSS scroll-driven animations on the compositor thread for guaranteed 60fps performance — MEDIUMScroll-Driven AnimationsCSS Responsive Patterns — HIGHCSS Responsive Patterns1. Container Query Basics2. Container Query Units (cqi, cqb)3. Fluid Typography with clamp()4. Container-Based Fluid Typography5. Mobile-First Breakpoints6. CSS Grid Responsive Patterns7. Container Scroll-Queries (Chrome 126+)Configure PWA offline support, manifest, and background sync for native-like installability — HIGHPWA Offline & InstallabilityConfigure service workers with Workbox 7.x caching strategies to prevent stale content — HIGHPWA Service Worker & WorkboxReact Responsive Patterns — HIGHReact Responsive PatternsResponsive Component with Container QueriesTailwind CSS Container QueriesuseContainerQuery HookResponsive Images PatternReferences (2)Container QueriesContainer Queries ReferenceContainer TypesQuery SyntaxContainer Query UnitsTailwind CSS v4 IntegrationBest PracticesFeature DetectionFluid TypographyFluid Typography ReferenceThe clamp() FormulaAccessible Fluid ScaleContainer-Based Fluid TypeCalculating Fluid ValuesAccessibility ConsiderationsLine Height ScalingTools