Enterprise UX · Native HTML/CSS · Progressive enhancement

Design for the smallest credible context.

A comprehensive starting point for responsive applications, no-JavaScript first rendering, modern native controls, accessible enterprise data, security, print, design systems, and deliberate JavaScript enhancement.

This handout contains no JavaScript. Every section, example, internal link, table, and print rule is available at first render.

Contents

Status: Living guidance
Last reviewed: 2026-07-14
Canonical source: This Markdown file
Derived companion: resilient-mobile-first-web-handbook.html

This handbook is an evidence-based starting point, not a rigid framework. Begin with semantic HTML and resilient CSS. Use JavaScript deliberately for state, data, focus, geometry, persistence, and complex interaction. Keep meaningful content and primary paths available when scripts fail, are blocked, or have not loaded yet.


1. Purpose#

This handbook consolidates implementation research, mobile-first product guidance, enterprise design review, and current web-platform capabilities for:

  • Native HTML and CSS before JavaScript
  • Mobile-first and responsive design
  • Progressive disclosure
  • Dashboards, modular blocks, sidebars, sticky headers, and complex data
  • Modern CSS architecture and feature detection
  • Tables, data grids, sticky rows and columns, highlighting, and merged cells
  • Drag, drop, ordering, dashboard placement, and free-form layouts
  • Print CSS
  • Modern CSS reset and starter layers
  • HTML/CSS comments for human and LLM takeover
  • Accessibility, security, performance, compatibility, responsible AI, privacy, and internationalization

The goal is not “zero JavaScript.”

The goal is:

Server- or document-rendered content first, CSS-owned presentation second, and the minimum JavaScript needed for state, data, focus, or complex interaction.

A prototype should normally show meaningful content immediately, even when JavaScript is disabled.


2. Core design principles#

2.1 Progressive enhancement#

Build in this order:

  1. Semantic HTML
  2. CSS layout and presentation
  3. Native browser behavior
  4. Server navigation and form submission
  5. Small JavaScript enhancement
  6. Full client-side state only when justified

Baseline content must not depend on hydration.

2.2 Meaning before layout#

  • Use HTML to define what the content is.
  • Use CSS to define how it is arranged.
  • Use JavaScript to manage state and behavior.
  • Use the server to enforce authorization and data integrity.

2.3 One useful page without JavaScript#

A generated prototype should normally:

  • Render headings, text, tables, forms, and navigation without JavaScript.
  • Use real links.
  • Use real form actions.
  • Avoid an empty root element waiting for client rendering.
  • Avoid hiding the entire page until initialization.
  • Avoid requiring JavaScript for first paint.

2.4 Capability, not ideology#

Use JavaScript when it improves or enables:

  • Asynchronous data
  • Optimistic updates
  • Persistent client state
  • Complex focus management
  • Tabs, comboboxes, trees, grids, and other composite widgets
  • Drag and drop
  • Virtualization
  • Live updates
  • Cross-component coordination
  • Dynamic announcements
  • Geometry and collision detection

3. Mobile-first enterprise product architecture#

Mobile-first is not a narrow-screen styling exercise. It is a product-architecture discipline.

Start with:

  • The most important user outcome
  • The smallest credible context
  • The weakest credible device and network
  • The least available attention
  • Coarse input and one-handed use
  • Interruption, stale data, and partial failure
  • The need for meaning to survive layout changes

3.1 The smallest credible context#

The smallest credible context is not necessarily the smallest phone.

It is the smallest combination of:

  • Viewport
  • Available time
  • Network quality
  • Device performance
  • Input precision
  • User attention
  • User familiarity
  • Authorization and data scope

The initial experience should remain useful under that combination.

The compact experience is the product's priority model made visible.

3.2 The first viewport is a decision surface#

The first viewport should answer:

  1. Where am I?
  2. What matters now?
  3. What can I do next?
  4. What happened after I acted?

Do not treat the compact home screen as a miniature portal containing one tile for every organizational function.

Prioritize:

  • Current scope
  • Current condition
  • Exceptions
  • Primary action
  • Immediate recovery
  • A route to deeper detail

3.3 Enterprise content priority#

Priority Purpose Typical content
Immediate Enable action now Current status, urgent exceptions, primary action
Supporting Enable a sound decision Critical context, summary metrics, dependencies
Investigative Enable diagnosis History, details, logs, related records
Administrative Enable configuration Metadata, permissions, infrequent controls

Immediate and supporting information should normally dominate the compact initial viewport.

Investigative and administrative content should remain reachable through clear navigation and progressive disclosure rather than being removed.

3.4 Responsive, adaptive, and reactive behavior#

Mode Meaning Preferred owner
Responsive Dimensions and arrangement respond to available space CSS
Component-responsive A module responds to its own container Container queries
Adaptive Composition or navigation model changes by context HTML/CSS with selective JavaScript
Reactive Interface responds to application data or state Server or JavaScript
Progressive Advanced behavior is layered over a functioning baseline HTML → CSS → JavaScript

A compact and expanded composition may legitimately differ:

Compact                     Expanded
──────────────────          ─────────────────────────────
Short app bar               Persistent top bar
Single content pane         Navigation + content
Bottom destinations         Side navigation
Record list                 List-detail split view
Filter disclosure/dialog    Persistent filter panel
Overflow actions            Visible contextual toolbar

The information architecture remains consistent even when the composition changes.

3.5 Discoverability before decorative minimalism#

Primary paths should be visible.

Prefer:

  • Visible high-frequency destinations
  • A clear current-location indicator
  • Text labels for unfamiliar icons
  • Actions adjacent to the object they affect
  • Visible search when retrieval is central
  • Overflow menus for secondary actions
  • Explicit destructive-action wording
  • Persistent labels rather than placeholder-only fields

Avoid:

  • Gesture-only essential actions
  • Hover-only controls
  • Unlabeled custom icons
  • Hidden destructive consequences
  • Ambiguous “More” actions without context
  • Hamburger-only primary navigation by default
  • Removing context to make a screen appear visually quiet

3.6 Mobile app shell#

A default mobile application shell should use:

  • A compact top header
  • One document-scrolling surface
  • A single primary content pane
  • Three to five high-frequency destinations when bottom navigation is justified
  • A complete secondary navigation route
  • Safe-area padding for edge-aligned controls
  • Content padding that prevents bottom controls from covering content
  • Real links for destinations
<meta
  name="viewport"
  content="width=device-width, initial-scale=1, viewport-fit=cover">
.bottom-navigation {
  padding-block-end:
    max(0.75rem, env(safe-area-inset-bottom));
}

main {
  padding-block-end:
    calc(var(--bottom-navigation-height) + env(safe-area-inset-bottom));
}

Do not add bottom navigation merely because the viewport is narrow. Use it only for a small, stable set of frequent destinations.

3.7 Touch and coarse-input design#

Treat WCAG 2.2's 24 × 24 CSS-pixel target requirement as a floor.

Prefer approximately 44–48 CSS pixels for standalone mobile controls where space permits.

Also validate:

  • Separation between adjacent destructive and safe actions
  • One-handed reach
  • Accidental activation
  • Controls near device edges
  • Sticky controls under browser chrome
  • External keyboard operation
  • Switch and voice-control naming

Do not rely on swipe, drag, long press, or hover as the only method.

3.8 State completeness and resilience#

Loading, saving, stale, offline, pending, failed, unauthorized, empty, and complete are different states.

Every data-bearing component should define:

  • Loading
  • Empty
  • Populated
  • Partial
  • Stale
  • Offline
  • Failed
  • Unauthorized or restricted
  • Saving or synchronizing
  • Success

A failure state should explain:

  • What failed
  • Whether user data was preserved
  • Whether the operation may have completed
  • What the user can do next
  • Whether retry is safe
  • Where to obtain more detail

3.9 Forms and recovery#

Mobile forms should reduce typing, uncertainty, and rework.

Use:

  • Persistent labels
  • Correct input types and inputmode
  • Appropriate autocomplete
  • Explicit optional markers
  • Examples and constraints near fields
  • Server-authoritative validation
  • Error summaries linked to fields
  • Valid input preservation
  • Save-and-resume for long workflows
  • Clear submission and pending states

Do not erase valid data after one field fails.

3.10 Complex data on mobile#

Transform data density according to the task rather than shrinking everything.

User task Preferred compact pattern
Find or open a record Structured list
Compare records across attributes Contained semantic table
Investigate one record Separate record view
Monitor current condition Exception-first dashboard
Edit many cells Purpose-built grid with keyboard and touch alternatives

Do not convert every table into cards. Cards improve individual-record scanning but weaken cross-record comparison.

3.11 Responsible design#

Enterprise mobile interfaces should preserve user agency.

  • Request data only when needed.
  • Explain why a permission is required.
  • Separate required and optional data.
  • Avoid preselected consent.
  • Make accept and reject choices equally understandable.

Consequential actions#

  • Explain consequences.
  • Confirm high-risk changes.
  • Provide undo when feasible.
  • Expose role and authorization boundaries.
  • Preserve audit history.

AI-assisted experiences#

  • Identify AI-generated or transformed content.
  • Show relevant provenance.
  • Distinguish observed facts from inference.
  • Indicate material uncertainty.
  • Keep human approval for consequential actions.
  • Provide correction and escalation paths.

Internationalization#

  • Test long translated strings.
  • Support right-to-left layouts.
  • Use local date, time, currency, units, and number formats.
  • Avoid physical left/right assumptions in component contracts.
  • Test mixed-script identifiers and localized legal content.

3.12 Enterprise quality model#

High production value is system integrity, not visual novelty.

It requires:

  • Clear hierarchy
  • Consistent rhythm
  • Complete component states
  • Strong typography
  • Predictable motion
  • Accessible interaction
  • Responsive composition
  • Error recovery
  • Performance under realistic conditions
  • Security and privacy clarity
  • Coherent content design

A beautiful application with poor task completion should fail review.


4. Ownership model#

Concern Preferred owner
Document meaning HTML
Reading order DOM
Navigation Links and forms
Baseline disclosure <details> and <summary>
Modal surface <dialog>
Layout CSS
Responsive adaptation CSS media/container queries
Visual state CSS selectors and custom properties
Application state JavaScript or server
Authorization Server
Validation HTML first, server authoritative
Async data JavaScript
Persistence Server or storage through JavaScript
Print redaction Server
Print presentation CSS
Drag coordinates/order JavaScript
Canonical order Data model and DOM

5. Default page architecture#

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Prototype</title>
  <link rel="stylesheet" href="/styles/index.css">
</head>
<body>
  <a class="skip-link" href="#main-content">Skip to main content</a>

  <header class="site-header">
    <a href="/" class="brand">Product</a>
  </header>

  <div class="app-shell">
    <nav class="primary-navigation" aria-label="Primary">
      <ul role="list">
        <li><a href="/overview" aria-current="page">Overview</a></li>
        <li><a href="/activity">Activity</a></li>
        <li><a href="/settings">Settings</a></li>
      </ul>
    </nav>

    <main id="main-content" tabindex="-1">
      <h1>Overview</h1>
      <p>Meaningful content is visible before JavaScript runs.</p>
    </main>
  </div>
</body>
</html>

Rules#

  • Use one <main>.
  • Use <nav> for navigation, even when visually positioned as a sidebar.
  • Use <aside> only for complementary content.
  • Use source order that remains meaningful at every breakpoint.
  • Use one primary page scrolling surface by default.

6. Native HTML before JavaScript#

6.1 Disclosure#

<details>
  <summary>Advanced settings</summary>
  <p>Secondary content.</p>
</details>

Use for:

  • FAQs
  • Optional explanations
  • Advanced settings
  • Secondary metadata
  • Simple navigation groups

Use independent disclosures by default. Add a shared name only when one-open-at-a-time behavior is explicitly required.

Keep errors, critical warnings, required instructions, current scope, and primary actions visible.

6.2 Declarative dialogs with Invoker Commands#

Invoker Commands are Baseline 2025 Newly Available.

Current evergreen browsers can open, close, and request closure of dialogs declaratively, without waiting for JavaScript.

<button
  type="button"
  commandfor="delete-dialog"
  command="show-modal">
  Delete project
</button>

<dialog
  id="delete-dialog"
  aria-labelledby="delete-title">

  <h2 id="delete-title">Delete project?</h2>
  <p>This action cannot be undone.</p>

  <div class="cluster">
    <button
      type="button"
      commandfor="delete-dialog"
      command="request-close"
      autofocus>
      Cancel
    </button>

    <form action="/projects/123/delete" method="post">
      <button type="submit">Delete project</button>
    </form>
  </div>
</dialog>

Available built-in commands include:

  • show-modal
  • close
  • request-close
  • show-popover
  • hide-popover
  • toggle-popover

request-close triggers the dialog's cancelable close-request behavior. Use it when application code may need to prevent closure, such as when unsaved work exists.

<form method="dialog"> remains an effective no-script close-and-return-value pattern for local dialog choices.

Compatibility rule#

Invoker Commands are Newly Available, not Widely Available.

For products supporting older browsers:

  1. Keep the declarative attributes in the HTML.
  2. Feature-detect command support.
  3. Load a small fallback only when missing.
  4. Do not attach an unconditional handler in supporting browsers.
const supportsInvokerCommands =
  "commandForElement" in HTMLButtonElement.prototype;

if (!supportsInvokerCommands) {
  document.addEventListener("click", (event) => {
    const button = event.target.closest("button[commandfor][command]");
    if (!(button instanceof HTMLButtonElement)) return;

    const target = document.getElementById(
      button.getAttribute("commandfor") ?? "",
    );

    if (!(target instanceof HTMLDialogElement)) return;

    switch (button.getAttribute("command")) {
      case "show-modal":
        target.showModal();
        break;
      case "close":
        target.close(button.value);
        break;
      case "request-close":
        if ("requestClose" in target) {
          target.requestClose(button.value);
        } else {
          target.close(button.value);
        }
        break;
    }
  });
}

Always include an explicit close or cancel control.

The newer closedby attribute can control explicit close, platform close requests, and light-dismiss behavior. It remains an Interop 2026 focus and should not be the only close mechanism until the project browser floor verifies it.

6.3 Popovers#

Ordinary popovers already work declaratively:

<button type="button" popovertarget="account-actions">
  Account actions
</button>

<div id="account-actions" popover="auto">
  <nav aria-label="Account actions">
    <a href="/profile">Profile</a>
    <a href="/settings">Settings</a>
    <form action="/logout" method="post">
      <button>Sign out</button>
    </form>
  </nav>
</div>

Invoker Commands can provide a uniform command model:

<button
  type="button"
  commandfor="account-actions"
  command="toggle-popover">
  Account actions
</button>

Use:

  • auto for ordinary light-dismiss popovers
  • manual for independently controlled persistent popovers
  • hint only as progressive enhancement for tooltip-like content that should coexist with an open auto popover

Do not make required instructions available only through a tooltip or hint popover.

A visual list of navigation links is not automatically an ARIA application menu.

6.4 Search semantics#

Use <search> to identify search or filtering controls:

<search aria-label="Search records">
  <form action="/records" method="get">
    <label for="record-query">Search records</label>
    <input
      id="record-query"
      name="q"
      type="search"
      autocomplete="off">
    <button>Search</button>
  </form>
</search>

JavaScript may enhance the form with suggestions or partial updates. The GET route remains the baseline.

6.5 Forms#

Use native constraints first:

<form action="/accounts" method="post">
  <label>
    Work email
    <input type="email" name="email" autocomplete="email" required>
  </label>

  <button type="submit">Create account</button>
</form>

Provide visible format guidance, field-level errors, and an error summary. Server validation remains authoritative.

6.6 Inert regions#

The inert attribute makes a subtree unavailable to focus and activation.

Use it for genuinely inactive interface regions, such as background content during a custom non-dialog workflow.

<main inert>
  ...
</main>

Prefer native modal <dialog> where possible because showModal() manages background inertness.

Do not combine inert with content that the user still needs to read or operate.

6.7 Discoverable hidden content#

hidden="until-found" can keep secondary content out of normal rendering while allowing browser find-in-page or fragment navigation to reveal it.

Use it only as progressive enhancement.

Do not use ordinary hidden for print-only or responsive-only content.

6.8 Responsive images#

<picture>
  <source
    media="(width >= 70rem)"
    srcset="/images/dashboard-wide.avif"
    type="image/avif">
  <img
    src="/images/dashboard-small.jpg"
    alt="Dashboard overview"
    width="800"
    height="600"
    loading="lazy">
</picture>

Do not lazy-load the likely LCP image.

6.9 Declarative Shadow DOM#

Declarative Shadow DOM can server-render an encapsulated shadow tree using <template shadowrootmode="open">.

Use it only when encapsulation provides material value.

On unsupported browsers, ordinary template content remains hidden. Do not make Declarative Shadow DOM the sole baseline path unless the browser floor supports it or a light-DOM fallback exists.


6.10 Native file upload#

The baseline upload path is an ordinary form.

<form
  action="/evidence"
  method="post"
  enctype="multipart/form-data">

  <fieldset>
    <legend>Upload evidence</legend>

    <label for="evidence-files">Files</label>

    <input
      id="evidence-files"
      name="evidence"
      type="file"
      accept=".pdf,image/*"
      multiple
      aria-describedby="evidence-help">

    <p id="evidence-help">
      PDF, PNG, or JPEG. Up to 10 files and 25 MB per file.
    </p>

    <button type="submit">Upload files</button>
  </fieldset>
</form>

This works without JavaScript.

Requirements:

  • Use method="post".
  • Use enctype="multipart/form-data".
  • Give the input a visible label.
  • State allowed formats, quantity, and size near the control.
  • Use multiple only when several files are part of the task.
  • Use accept as a file-picker hint, not as validation.
  • Preserve a normal submit path when adding previews, drag/drop, or asynchronous upload.
  • Report server rejection in an error summary and beside the file control.
  • Explain whether rejected files must be reselected.

Mobile capture#

For a workflow that explicitly asks the user to capture a new image:

<label for="receipt-photo">Photograph receipt</label>

<input
  id="receipt-photo"
  name="receipt"
  type="file"
  accept="image/*"
  capture="environment">

capture="user" prefers the user-facing camera. capture="environment" prefers the outward-facing camera.

Treat capture as a user-agent hint. Do not assume it will always open a particular device or prevent choosing an existing file.

Directory selection#

webkitdirectory can enable directory selection in supporting browsers:

<input type="file" name="project-files" webkitdirectory multiple>

It remains non-standard. Use it only as a progressive enhancement and retain ordinary multi-file or archive upload.

Styled file-picker trigger#

Prefer the native file input.

When a custom visual trigger is necessary, associate a <label> with a visually hidden input. Do not hide the input with display: none when the label is expected to provide the keyboard-accessible activation path.

<input
  class="visually-hidden"
  id="attachments"
  name="attachments"
  type="file"
  multiple>

<label class="button" for="attachments">
  Choose attachments
</label>

Make the label display a visible focus indicator when its file input is focused.

6.11 File API enhancement#

After the user selects files, JavaScript may inspect FileList and File objects for:

  • Name
  • Size
  • Reported media type
  • Last-modified timestamp
  • Local preview
  • Client-side processing
  • Hashing or chunk preparation
<label for="documents">Documents</label>
<input id="documents" name="documents" type="file" multiple>

<p>
  <output id="document-summary" for="documents">
    No files selected.
  </output>
</p>
const input = document.querySelector("#documents");
const output = document.querySelector("#document-summary");

input?.addEventListener("change", () => {
  const files = Array.from(input.files ?? []);
  const bytes = files.reduce((total, file) => total + file.size, 0);

  output.textContent =
    files.length === 0
      ? "No files selected."
      : `${files.length} files, ${bytes.toLocaleString()} bytes`;
});

<output> is appropriate for a calculated or interaction result. Its value is not submitted with the form.

For previews:

  • Prefer URL.createObjectURL(file) for large binary previews.
  • Call URL.revokeObjectURL() when the preview is removed.
  • Do not interpret an uploaded document as trusted active HTML.
  • Do not use a client preview as evidence that the server will accept the file.

6.12 Upload progress#

Use <progress> for task completion:

<label for="upload-progress">Uploading files</label>
<progress id="upload-progress" max="100" value="45">
  45%
</progress>

Omit value for indeterminate progress:

<progress aria-label="Preparing upload"></progress>

Use <meter> for a measurement within a known range, not for a task:

<label for="storage-used">Storage used</label>
<meter
  id="storage-used"
  min="0"
  max="100"
  low="60"
  high="85"
  optimum="20"
  value="72">
  72%
</meter>

Examples for <meter> include storage consumption, signal quality, score, capacity, or quota.

Upload progress is not available from a normal form submission.

For enhanced upload progress:

  • XMLHttpRequest.upload exposes upload progress events.
  • Standard Fetch upload-progress events remain unavailable.
  • A server-side processing job may need a separate status endpoint after the transfer completes.
  • Do not show artificial percentage movement when the application cannot measure progress.

Always provide textual state such as “Uploading 2 of 5 files” in addition to the visual bar.

6.13 File upload security contract#

Browser attributes do not secure an upload.

The server must:

  • Authenticate and authorize the uploader.
  • Apply CSRF protection.
  • Allowlist business-required extensions.
  • Validate decoded filenames.
  • Generate internal storage names.
  • Enforce file-count, request-size, file-size, and decompressed-size limits.
  • Treat client-provided MIME type as untrusted.
  • Inspect file signatures where applicable.
  • Scan or sandbox files where appropriate.
  • Apply content disarm and reconstruction for supported high-risk documents where required.
  • Store uploads outside the executable web root or on a segregated service.
  • Apply least-privilege storage permissions.
  • Prevent uploaded active content from executing in the application origin.
  • Serve downloads with an intentional content type and disposition.
  • Audit upload, processing, access, rejection, and deletion.
  • Define retention and deletion policies.
  • Rate-limit abuse and protect storage capacity.
  • Validate archives and extracted paths against traversal and expansion attacks.

A successful client-side check must never bypass server processing.

6.14 File and storage capability tiers#

Capability Baseline path Enhancement
Upload files to server <input type="file"> and multipart form Preview, async upload, progress, chunking
Read user-selected file File input File API
Accept dropped files File input remains available HTML Drag and Drop
Select a directory Multi-file or archive upload webkitdirectory
Open/save a local file Download/upload links File System Access API
Private persistent app files Server or IndexedDB Origin Private File System
Export generated data Server download link Blob/object URL or file picker

The File System API and File System Access API are JavaScript APIs. They are not substitutes for a file-upload form.

Local file-system access requires a secure context and explicit user participation. Browser support and permission behavior must be checked for the project target.

6.15 Grouped controls#

Use <fieldset> and <legend> for a group with one shared question or constraint:

<fieldset>
  <legend>Notification method</legend>

  <label>
    <input type="radio" name="notification" value="email">
    Email
  </label>

  <label>
    <input type="radio" name="notification" value="sms">
    Text message
  </label>
</fieldset>

disabled on a fieldset disables its descendant controls, except for controls inside its first legend.

Do not replace a legend with a visually styled generic heading when the fields form one control group.

6.16 Native selection controls#

Use <select> when the user must choose from a bounded list.

Use <optgroup> for meaningful option categories:

<label for="region">Region</label>

<select id="region" name="region">
  <option value="">Choose a region</option>

  <optgroup label="Americas">
    <option value="us-east">US East</option>
    <option value="br-south">Brazil South</option>
  </optgroup>

  <optgroup label="Europe">
    <option value="eu-west">Europe West</option>
  </optgroup>
</select>

Use <datalist> only as an optional suggestion enhancement. It remains uneven across browsers and assistive technologies. The associated input must still accept and validate ordinary typed values.

Customizable selects and <selectedcontent> remain assess-only.

6.17 Input-type selection#

Prefer the native input type that matches the value.

Data Native control
Email type="email"
Telephone type="tel"
Web address type="url"
Search type="search"
Integer or decimal with numeric semantics type="number"
Bounded approximate value type="range" plus visible value
Date type="date"
Time type="time"
Local date and time type="datetime-local"
Month type="month"
Week type="week"
Color type="color"
File type="file"
Boolean Checkbox
One choice from a small set Radio group
Secret type="password"

Caveats:

  • Native date/time presentation varies by locale and browser.
  • type="number" is not appropriate for account numbers, ZIP/postal codes, card numbers, identifiers, or values with meaningful leading zeroes.
  • A range input needs a visible current value, bounds, and keyboard-accessible alternatives when precision matters.
  • Do not use type="hidden" for authorization or trustworthy business state.
  • Use inputmode to improve the keyboard without misrepresenting value semantics.
  • Use enterkeyhint when the expected mobile keyboard action is clear.

6.18 Description lists and machine-readable values#

Use <dl>, <dt>, and <dd> for metadata or term/value relationships:

<dl class="record-metadata">
  <dt>Owner</dt>
  <dd>Platform Engineering</dd>

  <dt>Last updated</dt>
  <dd>
    <time datetime="2026-07-14T14:30:00-05:00">
      July 14, 2026 at 2:30 PM
    </time>
  </dd>
</dl>

Use <time datetime> for machine-readable dates and times.

Use <data value> when visible content has a non-time machine-readable value:

<data value="SKU-1048">Enterprise gateway</data>

6.19 Figures, captions, and quotations#

Use <figure> with <figcaption> for a self-contained chart, diagram, code example, image, or table-like illustration that has its own caption.

<figure>
  <img
    src="/architecture.svg"
    alt="Request flow from browser through gateway to model provider">
  <figcaption>
    Enterprise AI gateway request flow.
  </figcaption>
</figure>

Use:

  • <q> for a short inline quotation
  • <blockquote> for a longer quotation
  • <cite> for the title of a cited work
  • <abbr> for abbreviations when expansion is useful
  • <mark> for contextually relevant highlighting
  • <ins> and <del> for document changes
  • <kbd> for user input
  • <samp> for program output
  • <var> for variables

Do not choose these elements only for their default visual style.

6.20 International text semantics#

Use:

  • <bdi> to isolate user-generated or unknown-direction text
  • <bdo dir> only when direction must be explicitly overridden
  • <ruby>, <rt>, and <rp> for pronunciation or annotation
  • <wbr> to expose safe break opportunities in long identifiers
  • lang on content that changes language
  • dir="auto" for isolated user-provided strings when appropriate
<p>
  User:
  <bdi dir="auto">إبراهيم</bdi>
</p>

Do not insert <wbr> into values that users need to copy exactly unless the break does not alter copied text.

6.21 Media elements#

Use native <audio> and <video> with controls.

Provide fallback download content:

<video controls playsinline preload="metadata" poster="/poster.jpg">
  <source src="/overview.webm" type="video/webm">
  <source src="/overview.mp4" type="video/mp4">

  <track
    default
    kind="captions"
    srclang="en"
    label="English"
    src="/overview.en.vtt">

  <p>
    <a href="/overview.mp4">Download the video</a>.
  </p>
</video>

Requirements:

  • Provide captions for prerecorded spoken video.
  • Provide a transcript when the content or audience requires it.
  • Use audio descriptions or an equivalent descriptive alternative when visual information is essential.
  • Avoid autoplay with sound.
  • Use playsinline for embedded mobile playback.
  • Use preload="metadata" or none unless preloading is justified.
  • Give users playback control.
  • Do not place essential instructions only in video.

6.22 SVG, MathML, and canvas#

Use inline SVG for:

  • Icons
  • Diagrams
  • Charts
  • Scalable illustrations

Give meaningful standalone SVGs an accessible name. Hide decorative SVGs from the accessibility tree.

Use MathML for mathematical notation when semantics matter.

Use <canvas> only when a bitmap drawing surface is materially required.

Canvas requires:

  • Fallback DOM content
  • A text or table alternative for important data
  • Keyboard-operable controls outside the canvas
  • High-resolution rendering
  • Print and export consideration
  • Explicit reduced-motion behavior

Do not make canvas pixels the only representation of critical information.

6.23 Table helper elements#

In addition to caption, thead, tbody, rows, and cells, use:

  • <tfoot> for totals, summaries, and column footers
  • <colgroup> and <col> for column-level presentation hooks
  • Multiple <tbody> elements for meaningful row groups

Do not use <col> as a replacement for cell semantics. Styling support on columns is intentionally limited.

6.24 Embedded content#

For an <iframe>:

<iframe
  title="Service region map"
  src="https://maps.example/embed"
  loading="lazy"
  sandbox="allow-scripts allow-same-origin"
  allow="fullscreen"
  referrerpolicy="strict-origin-when-cross-origin">
</iframe>

Define:

  • A concise title
  • sandbox
  • A minimal allow permissions policy
  • referrerpolicy
  • loading
  • Explicit width/aspect behavior
  • A fallback link
  • A documented origin and trust model

Every iframe creates a separate browsing context and consumes resources.

Avoid <object> and <embed> for new application features when an image, media element, iframe, download, or purpose-built viewer is available.

<fencedframe> is not a general application-embedding primitive.

6.25 Templates and component slots#

Use <template> for inert markup that JavaScript will instantiate.

Use <slot> only inside a Web Component shadow tree.

A template is not a no-JavaScript disclosure. Its contents are not rendered by default.

Document:

  • Who instantiates it
  • What data is inserted
  • How insertion is sanitized
  • The fallback when scripting is unavailable
  • Whether the component uses light DOM or shadow DOM

6.26 noscript#

<noscript> can provide a script-disabled notice, stylesheet, or alternate link.

<noscript>
  <p>
    Live filtering is unavailable. Use the
    <a href="/records/search">server search page</a>.
  </p>
</noscript>

Do not use <noscript> to duplicate the entire application.

The ordinary HTML should already contain the core content and paths.

6.27 Emerging elements to monitor#

Element Status Handbook posture
<geolocation> Experimental and limited Research only
<selectedcontent> Limited; customizable selects Assess only
<fencedframe> Specialized privacy/advertising context Not a general prototype primitive
<menu> Equivalent to unordered list in current browsers Use only when semantics genuinely improve source clarity
<map> and <area> Stable but niche Use only for genuine image maps with accessible equivalent
<object> and <embed> Stable but risky/legacy-heavy Avoid for new application workflows

Do not adopt an element merely because it appears in the element reference.

6.28 Native-element generation checklist#

Before creating a generic div, ask:

  • Is this navigation?
  • Is this an independently reusable article?
  • Is this complementary content?
  • Is this a search/filter region?
  • Is this a term/value list?
  • Is this a figure with a caption?
  • Is this a time or machine-readable value?
  • Is this a grouped form question?
  • Is this task progress or a scalar measurement?
  • Is this a file-upload form?
  • Is this media with captions?
  • Is this tabular data?
  • Is this a disclosure or dialog?
  • Is this quoted or edited content?
  • Does international text need isolation or annotation?

Use the semantic element when its meaning matches. Do not force a semantic element where the content model does not fit.


7. When JavaScript is required#

Add JavaScript when any answer is yes:

  1. Does the interaction require business state?
  2. Must state survive navigation or reload?
  3. Must data be fetched without page navigation?
  4. Must state changes be announced dynamically?
  5. Does the widget require managed focus?
  6. Does it require arrow-key navigation or roving tabindex?
  7. Does it require asynchronous search or options?
  8. Does it coordinate several independent components?
  9. Does the target browser floor lack required native behavior?
  10. Would the fallback hide essential information?
  11. Does the action require rollback or error recovery?
  12. Must the state be audited or synchronized?

7.1 Enhancement-only controls#

Do not display a button that is inert when JavaScript is unavailable.

Examples include:

  • Theme toggles
  • Copy-link buttons
  • Client-side search toggles
  • Reset-local-state controls
  • Reading-progress indicators
  • Layout-density toggles
  • Client-only print buttons

Hide enhancement-only controls until their module initializes:

<button hidden data-js-control="copy-link" type="button">
  Copy link
</button>
const control = document.querySelector('[data-js-control="copy-link"]');

if (control instanceof HTMLButtonElement) {
  control.hidden = false;
  control.addEventListener("click", copyCurrentLink);
}

Where possible, provide a baseline equivalent:

  • Real link instead of client navigation
  • GET form instead of client-only search
  • OS color scheme instead of mandatory theme toggle
  • Browser print command instead of a required print button
  • Native checkboxes even when persistence is unavailable

7.2 Never create a blank hydration shell by default#

Avoid:

<div id="root"></div>
<script src="/app.js"></script>

Prefer server-rendered or static initial markup:

<div id="root">
  <main>
    <h1>Account overview</h1>
    <p>Current account information...</p>
  </main>
</div>
<script type="module" src="/enhance.js"></script>

Client frameworks may hydrate or enhance the existing markup.

7.3 Responsive JavaScript work#

When JavaScript performs substantial filtering, rendering, or transformation:

  • Show immediate state feedback.
  • Break long work into bounded chunks.
  • Prefer workers for CPU-heavy independent work.
  • Use requestAnimationFrame() for visual updates.
  • Feature-detect newer scheduling APIs.
  • Retain a fallback scheduling strategy.

scheduler.yield() can improve responsiveness in supporting browsers, but it is not currently a broad Baseline feature. Do not depend on it without a fallback.

8. Responsive and mobile-first layout#

8.1 Start narrow#

.app-shell {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  padding: 1rem;
}

@media (min-width: 64rem) {
  .app-shell {
    grid-template-columns:
      minmax(14rem, 18rem)
      minmax(0, 1fr);
    align-items: start;
  }
}

8.2 Use intrinsic layouts#

.dashboard-grid {
  display: grid;
  grid-template-columns:
    repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: 1rem;
}

8.3 Media queries versus container queries#

  • Media queries govern page-level adaptation.
  • Container queries govern reusable component adaptation.
.dashboard-module {
  container-type: inline-size;
}

@container (min-width: 36rem) {
  .module-layout {
    grid-template-columns: minmax(0, 2fr) minmax(12rem, 1fr);
  }
}

8.4 Prevent intrinsic overflow#

.grid-child,
.flex-child {
  min-inline-size: 0;
}

Use minmax(0, 1fr) for flexible content tracks that may contain tables, code, or long identifiers.


8.5 Viewport and safe-area policy#

Use:

<meta
  name="viewport"
  content="width=device-width, initial-scale=1, viewport-fit=cover">

Do not disable user scaling.

Use safe-area environment variables only for controls or surfaces that reach physical screen edges.

8.6 Viewport-height policy#

Use svh for a stable minimum viewport that avoids being obscured by browser chrome.

Use dvh only when continuous resizing as browser chrome changes is desirable.

.app-page {
  min-block-size: 100vh;
  min-block-size: 100svh;
}

Do not use fixed 100vh application shells as the universal default.

8.7 Content-driven breakpoints#

Add a breakpoint when:

  • A control label wraps badly
  • A table or toolbar loses its intended relationship
  • Reading measure becomes excessive
  • Navigation becomes crowded
  • A split view gains enough room to improve the task

Do not create a breakpoint solely because a popular device has that width.

8.8 Mobile navigation decision#

Use bottom navigation only when:

  • There are approximately three to five stable high-frequency destinations.
  • Each destination has a real URL.
  • The complete navigation remains reachable.
  • The current destination is explicit.
  • Content is padded above the fixed navigation.
  • Safe-area insets are handled.
  • The bar remains usable under zoom and localization.

Otherwise prefer a compact top-level link set, disclosure, or dialog-based navigation.

8.9 Horizontal overflow failure modes#

A page can become horizontally scrollable even when the primary wrapper appears correctly sized.

Common causes include:

  • Long URLs, filenames, hashes, and unbroken identifiers
  • Inline code with no safe wrapping opportunity
  • Flex or Grid children retaining their intrinsic minimum width
  • pre, tables, SVG, canvas, media, or iframes exceeding their container
  • A positioned dropdown whose width is calculated from the viewport rather than its available edge
  • 100vw inside a page with classic scrollbars
  • Negative margins, transforms, or sticky elements extending beyond the scrollport
  • A width expression that is accepted by one engine but rejected by another

Use explicit containment at the component that owns the wide content:

.wrapper {
  inline-size:
    min(
      calc(100% - (2 * var(--page-gutter))),
      var(--content-max)
    );
  max-inline-size: 100%;
  margin-inline: auto;
}

.article,
.article > *,
.grid-child,
.flex-child {
  min-inline-size: 0;
}

.article :where(a, code, p, li, dd, td, th) {
  overflow-wrap: anywhere;
}

pre,
.table-scroll,
.code-scroll {
  max-inline-size: 100%;
  overflow-x: auto;
}

Use local horizontal scrolling for code and genuine two-dimensional data. Wrap ordinary prose, links, and identifiers.

Do not hide the defect globally with body { overflow-x: hidden; }. That can make content unreachable and conceal a broken positioned component. overflow-x: clip is a final defensive boundary only after the responsible component has been corrected.

Overflow regression check#

At every supported compact width, verify:

const root = document.documentElement;

if (root.scrollWidth > root.clientWidth) {
  throw new Error(
    `Horizontal overflow: ${root.scrollWidth - root.clientWidth}px`,
  );
}

When the check fails, inspect element rectangles and scrollWidth values rather than guessing from the visible symptom.

9. Tables, Flexbox, Grid, and normal flow#

Requirement Primitive
Sequential prose Normal flow
One-dimensional control group Flexbox
Two-dimensional layout Grid
Shared nested alignment Subgrid
Relational row/column data <table>
Spreadsheet interaction ARIA grid plus JavaScript

Flexbox#

Best for:

  • Toolbars
  • Navigation clusters
  • Button groups
  • Avatar/text rows
  • Wrapping tags

Prefer auto margins for group separation:

.toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.toolbar__end {
  margin-inline-start: auto;
}

Grid#

Best for:

  • App shells
  • Dashboard modules
  • Form alignment
  • Repeating card layouts
  • Two-dimensional composition

Tables#

Use tables when the intersection of a row and column is meaningful.

Do not convert native table elements to Grid or Flexbox merely to make them responsive.

Wrap wide tables in a named horizontal scroll region.


10. Alignment#

Default recommendations#

  • Prefer logical start and end.
  • Use gap instead of child margins.
  • Use baseline alignment for mixed typography.
  • Use safe center where centering could clip.
  • Avoid space-between for unstable toolbars.
  • Avoid visual reordering that differs from DOM order.
  • Treat stretch as an explicit decision.
.metric-header {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

11. Progressive disclosure#

Keep visible:

  • Page purpose
  • Current state
  • Primary actions
  • Errors
  • Security warnings
  • Required instructions
  • Active scope

Good disclosure candidates:

  • Advanced settings
  • Secondary filters
  • History
  • Debug details
  • Audit metadata
  • Rare actions

Preferred escalation:

Visible content
  → details/summary
  → popover
  → non-modal dialog
  → modal dialog
  → scripted workflow

Do not hide important content merely because a container becomes narrow. Reflow before removing.

11.1 Native disclosure markers and icon alignment#

The native <summary> marker varies by browser, operating system, font metrics, and line height. It can appear vertically misaligned or consume unexpected inline space in compact headers.

Keep the native marker when its platform appearance is acceptable.

When consistent alignment is required, preserve the <details> and <summary> semantics but replace only the visual marker:

summary {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-block-size: 2.75rem;
  list-style: none;
}

summary::-webkit-details-marker {
  display: none;
}

summary::after {
  content: "";
  flex: none;
  inline-size: 0.55rem;
  block-size: 0.55rem;
  margin-inline-start: auto;
  border-inline-end: 0.125rem solid currentColor;
  border-block-end: 0.125rem solid currentColor;
  transform: rotate(45deg);
}

details[open] > summary::after {
  transform: rotate(225deg);
}

Requirements:

  • Keep the whole summary row as the activation target.
  • Preserve a visible focus indicator.
  • Use currentColor so the marker remains visible in forced-colors modes.
  • Do not add a second icon while leaving the native marker visible.
  • Do not use an icon font for essential disclosure state.
  • Test multiline summaries, text zoom, localization, Safari on iPhone, and external keyboards.

12. Sticky headers and scrolling#

12.1 Prefer document scrolling#

Avoid:

body {
  overflow: hidden;
}

main {
  height: 100vh;
  overflow: auto;
}

unless the application truly requires an independent workspace.

12.2 Sticky header#

.site-header {
  position: sticky;
  inset-block-start: 0;
  z-index: 20;
  background: Canvas;
  border-block-end: 1px solid;
}

html {
  scroll-padding-block-start: 5rem;
}

:where(h1, h2, h3, h4, [id]) {
  scroll-margin-block-start: 5rem;
}

Rules#

  • Keep mobile sticky headers compact.
  • Avoid stacked sticky bars.
  • Do not auto-hide on scroll by default.
  • Ensure focused elements are not obscured.
  • Test at 200% and 400% zoom.

12.3 Sticky header compression#

A sticky header with a long identity and a fixed-width action can create page-level overflow.

Use a shrinkable identity and non-shrinking actions:

.site-header__inner {
  display: flex;
  align-items: center;
  gap: 1rem;
  max-inline-size: 100%;
}

.site-header__identity {
  flex: 1 1 auto;
  min-inline-size: 0;
  overflow: hidden;
}

.site-header__title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.site-header__actions {
  flex: none;
}

Use a shorter mobile action label before reducing text to an unreadable size.

12.4 Prevent stacked secondary sticky headings#

Several sticky headings that are siblings inside one long container share the same sticky containing block.

This can create a visible stack:

  • A long previous heading wraps to two lines.
  • The next heading reaches the same sticky inset.
  • The next heading covers only part of the previous heading.
  • The uncovered line remains visible underneath.
  • A guessed offset that is larger than the actual primary header creates a blank strip between the two sticky layers.
  • A guessed offset that is smaller creates overlap.

Do not solve this with overflow-x: hidden, a larger z-index, or an opaque rectangle over the content. Those approaches conceal symptoms without fixing the sticky geometry.

Scope each sticky heading to its chapter#

Wrap each top-level heading and its content in a section:

<article class="article">
  <section
    class="handbook-chapter"
    aria-labelledby="chapter-ownership">

    <h2 id="chapter-ownership">
      Ownership model
    </h2>

    <!-- Chapter content -->
  </section>

  <section
    class="handbook-chapter"
    aria-labelledby="chapter-architecture">

    <h2 id="chapter-architecture">
      Default page architecture
    </h2>

    <!-- Chapter content -->
  </section>
</article>
.handbook-chapter {
  position: relative;
  min-inline-size: 0;
  margin-block-start: var(--section-gap);
}

.handbook-chapter > h2 {
  position: sticky;
  inset-block-start: var(--primary-header-offset);
  z-index: 1;

  /*
   * Section spacing belongs to the section.
   * The sticky element itself must not carry a large top margin.
   */
  margin: 0;
  padding-block: 0.5rem;

  background: Canvas;
  border-block-end: 1px solid;
}

Because each heading is constrained by its own section, it is pushed out when that chapter ends before the next chapter heading occupies the sticky position.

Keep the offset and rendered header size synchronized#

When the primary header has a deliberately fixed compact height, use one custom property for both its actual block size and the secondary sticky inset:

:root {
  --primary-header-height: 4.25rem;
  --primary-header-offset:
    calc(
      var(--primary-header-height)
      + env(safe-area-inset-top)
    );
}

.site-header {
  block-size: var(--primary-header-offset);
}

.site-header__inner {
  block-size: 100%;
  padding-block-start: env(safe-area-inset-top);
}

.handbook-chapter > h2 {
  inset-block-start: var(--primary-header-offset);
}

Do not set the header to an unconstrained dynamic height while positioning the secondary layer with a guessed fixed number.

When the primary header must wrap or change height dynamically, choose one of these approaches:

  1. Remove the secondary sticky layer at that breakpoint.
  2. Keep the primary header to a guaranteed single-line compact composition.
  3. Measure the header with ResizeObserver and update a CSS custom property.
  4. Place both layers inside one deliberately managed sticky shell.

The first two approaches preserve zero-JavaScript behavior.

Compact-width validation#

At each supported compact width, verify:

  • Only one secondary chapter heading is visible in the sticky position.
  • No previous heading text remains underneath.
  • No content or table borders are visible in a gap between sticky layers.
  • The primary and secondary backgrounds are opaque.
  • Fragment navigation lands below both sticky layers.
  • Text zoom and localization do not make the primary header exceed its declared block size.
  • Printing resets both layers to static positioning.

13. Sidebars and mobile navigation#

Start with navigation in normal flow.

At larger sizes, make it sticky:

@media (min-width: 64rem) {
  .section-navigation {
    position: sticky;
    inset-block-start: 5rem;
  }
}

For a true mobile drawer, use <dialog> plus minimal JavaScript.

Do not use checkbox-based CSS drawer hacks for application navigation.


14. Dashboards and modular blocks#

A dashboard module should have:

  • One semantic root
  • A descriptive heading
  • Stable DOM order
  • Useful no-JavaScript content
  • A narrow-layout default
  • Explicit loading, empty, error, restricted, and populated states
  • Container-responsive styling when useful

Do not make every section a card.

Use cards for independently meaningful units. Use spacing, headings, and rules for ordinary document structure.


15. Data tables and complex data#

15.1 Semantic table#

<table>
  <caption>Service health by region</caption>
  <thead>
    <tr>
      <th scope="col">Region</th>
      <th scope="col">Availability</th>
      <th scope="col">Latency</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">US East</th>
      <td>99.98%</td>
      <td>42 ms</td>
    </tr>
  </tbody>
</table>

15.2 Responsive table region#

<div
  class="table-scroll"
  role="region"
  aria-labelledby="orders-heading"
  tabindex="0">
  <table>...</table>
</div>
.table-scroll {
  max-inline-size: 100%;
  overflow-x: auto;
}

15.3 Large data strategy#

Use in this order:

  1. Remove redundant columns.
  2. Shorten labels without losing meaning.
  3. Allow wrapping.
  4. Use a labeled scroll region.
  5. Add filtering and server pagination.
  6. Add a record-detail view.
  7. Add virtualization only after measuring a real need.

16. Sticky data grids#

Use one two-axis scroll container by default.

.data-grid-scroll {
  position: relative;
  isolation: isolate;
  max-block-size: min(70svh, 48rem);
  max-inline-size: 100%;
  overflow: auto;
}

.data-table {
  min-inline-size: max-content;
  border-collapse: separate;
  border-spacing: 0;
}

.data-table thead th {
  position: sticky;
  inset-block-start: 0;
  z-index: 3;
  background: Canvas;
}

.data-table tbody th[scope="row"] {
  position: sticky;
  inset-inline-start: 0;
  z-index: 2;
  background: Canvas;
}

.data-table thead th:first-child {
  inset-inline-start: 0;
  z-index: 4;
}

Sticky grid rules#

  • Sticky cells need opaque backgrounds.
  • Use explicit z-index layers.
  • Prefer border-collapse: separate.
  • Use logical inset properties.
  • Use JavaScript for cumulative offsets when header heights or frozen-column widths are dynamic.
  • Keep semantic table markup unless spreadsheet interaction is implemented.

17. Cell merging and highlighting#

Use rowspan and colspan only when data is semantically merged.

Avoid merged body cells in sortable, editable, virtualized, or reorderable grids.

For visual grouping, keep cells separate and layer backgrounds:

.data-table :is(th, td) {
  --cell-base: Canvas;
  --group-tint: transparent;
  --row-tint: transparent;
  --column-tint: transparent;
  --selection-tint: transparent;

  background:
    linear-gradient(var(--selection-tint), var(--selection-tint)),
    linear-gradient(var(--column-tint), var(--column-tint)),
    linear-gradient(var(--row-tint), var(--row-tint)),
    linear-gradient(var(--group-tint), var(--group-tint)),
    var(--cell-base);
}

Do not rely on color alone. Add borders, focus rings, and text labels.


18. Drag, drop, ordering, and placement#

18.1 Choose the model first#

Model Stored state
Ordered list Item sequence
Wrapped card grid Item sequence
Dashboard grid x, y, w, h
Bounded free-form surface Pixel/world coordinates
Infinite canvas World coordinates plus viewport transform
External file/data drop DataTransfer payload

18.2 Ordered content#

Store canonical order and render DOM in that order.

Do not use CSS order or transforms as permanent state.

Provide keyboard alternatives:

  • Move up/down
  • Move to start/end
  • Move before/after another item
  • Move to position

18.3 Dashboard grid#

Store logical coordinates and spans.

Define collision behavior:

  • Push
  • Swap
  • Compact
  • Free placement

Mobile should normally use semantic order rather than preserving desktop gaps.

18.4 Free movement#

Use Pointer Events and a dedicated drag handle.

  • Capture the pointer.
  • Use transforms for the preview.
  • Commit model coordinates at drop.
  • Batch movement with requestAnimationFrame().
  • Restrict touch-action: none to the handle.
  • Provide keyboard movement and coordinate entry.

18.5 Native HTML drag and drop#

Best for:

  • Files
  • URLs
  • Text
  • Cross-window or cross-application transfer

Not the default for touch-first in-app component movement.

18.6 Accessibility#

Every drag operation needs:

  • A non-dragging pointer alternative
  • A complete keyboard alternative
  • Focus restoration
  • Concise live-region announcements
  • Adequate touch target sizes

Do not use deprecated aria-grabbed or aria-dropeffect.


19. Modern CSS state management#

Use the narrowest owner.

State Owner
Theme <html>
Locale/direction <html lang dir>
App density Application root
Navigation expansion Navigation shell
Module loading Module
Field validity Native control
Dialog open Native dialog
Authorization Server

State channels:

  1. Native state: open, checked, disabled, invalid
  2. Semantic ARIA state
  3. Component data-* state
  4. Ancestor-derived state with :has()
  5. Downward propagation through custom properties
  6. JavaScript-owned application state

Use CSS for derived presentation, not business truth.


20. Feature detection and platform radar#

Use fallback-first CSS.

.module {
  display: block;
}

@supports (display: grid) {
  .module {
    display: grid;
  }
}

Selector detection:

@supports selector(:has(*)) {
  .field:has(:user-invalid) {
    border-inline-start: 0.25rem solid;
  }
}

JavaScript capability detection:

const capabilities = {
  invokerCommands:
    "commandForElement" in HTMLButtonElement.prototype,
  navigationAPI:
    "navigation" in window,
  viewTransitions:
    "startViewTransition" in document,
  schedulerYield:
    typeof globalThis.scheduler?.yield === "function",
};

Do not use browser-name detection.

20.1 Define a compatibility profile#

“Modern browsers” is not a sufficiently precise production target.

Choose one profile:

Profile Target Feature posture
Broad resilient Older supported devices and embedded browsers Baseline Widely Available plus fallbacks
Current evergreen Latest Chrome, Edge, Firefox, and Safari Baseline Newly Available may be a default with graceful fallback
Controlled runtime Managed webview, kiosk, or fixed browser Verified runtime capabilities may be required
Experimental Demonstration or research Limited features allowed; never implied to be production-safe

Record:

  • Browser versions
  • Embedded webviews
  • iOS floor
  • Enterprise support period
  • Required assistive technologies
  • Print engines
  • Feature exceptions

20.2 Broad resilient baseline#

Use normally:

  • Semantic HTML and normal document flow
  • Real links and forms
  • Grid and Flexbox
  • Media queries
  • Logical properties
  • <details> and <summary>
  • <dialog> with a conditional compatibility shim
  • Popover with an accessible fallback path
  • Sticky positioning
  • Responsive images
  • Native lazy loading
  • Semantic tables
  • Reduced-motion and forced-colors queries
  • Cascade layers
  • CSS nesting
  • Subgrid
  • Container size queries
  • :has() for non-critical derived presentation
  • <search>
  • inert

20.3 Current-evergreen capabilities#

These are interoperable across current browser releases but may be absent from older deployed devices:

Capability Baseline date Recommended use
Same-document View Transitions October 2025 Optional motion enhancement
@scope December 2025 Component selector boundaries
Invoker Commands December 2025 Declarative dialog/popover controls
Core anchor positioning January 2026 Floating-panel placement with fallback
Navigation API January 2026 SPA enhancement over real navigation
Trusted Types February 2026 DOM-XSS defense in depth
Container style queries 2026 Contextual component styling
:open 2026 Unified open-state styling
Custom highlights 2026 Non-essential text-range highlighting
field-sizing June 2026 Auto-growing form controls with bounds

For a current-evergreen project, Invoker Commands may replace the dialog-opening script as the default implementation.

For broad distribution, retain a conditional shim.

20.4 Progressive enhancements#

Use when ignored support remains functional:

  • Anchor positioning with a static placement fallback
  • @scope with ordinary low-specificity selectors as fallback
  • Same-document View Transitions with immediate updates as fallback
  • Navigation API over server-rendered links and routes
  • Container style queries
  • :open
  • Dynamic viewport units
  • Relative colors and color-mix()
  • @property
  • contrast-color() for constrained palettes only
  • CSS Custom Highlight API
  • field-sizing: content
  • Declarative Shadow DOM
  • content-visibility
  • Scroll snap
  • scrollbar-gutter
  • hidden="until-found"
  • Trusted Types

contrast-color() currently selects between black and white. Validate the resulting contrast rather than assuming every mid-tone palette is accessible.

20.5 Assess-only or controlled-environment features#

Do not make critical navigation, data access, form submission, recovery, or accessibility depend on:

  • <dialog closedby> until the browser floor is verified
  • popover="hint" as the sole instruction path
  • Interest invokers
  • Container scroll-state queries
  • Scroll-driven animations
  • Cross-document View Transitions
  • Typed attr() values
  • CSS if()
  • CSS custom functions or mixins
  • reading-flow or reading-order
  • Customizable selects
  • HTML Sanitizer API without a tested safe fallback
  • Generated scroll buttons and markers
  • Scoped custom-element registries
  • Element.moveBefore()
  • Grid-lane or masonry proposals
  • scheduler.yield() without fallback

reading-flow is not permission to ignore meaningful DOM order.

Element.moveBefore() may preserve state while moving nodes and could improve drag/reorder implementations, but it is not currently Baseline.

20.6 Correct interpretation of Interop work#

Interop participation and Baseline availability are related but not equivalent.

  • Interop 2025 focused on anchor positioning, same-document View Transitions, @scope, Navigation API, and mobile testing.
  • Same-document View Transitions reached Baseline in October 2025.
  • @scope and Invoker Commands reached Baseline in December 2025.
  • Core anchor positioning and Navigation API reached Baseline in January 2026.
  • Interop 2026 continues work on anchor positioning, dialogs, popovers, view transitions, container style queries, scroll snap, mobile testing, and related features.

Always verify the individual feature and property.

20.7 Research cadence#

Review this radar:

  • Quarterly for active prototype generation
  • Before adopting a newly available feature as a default
  • When the supported iOS floor changes
  • When a major enterprise browser policy changes
  • When WCAG, DTCG, or portable-design-context specifications change

Use primary sources:

  • Web Platform Baseline
  • Web Platform Status
  • Interop dashboards
  • MDN compatibility data
  • W3C specifications
  • Browser-engine release notes

21. iPhone input zoom#

iOS may zoom focused editable controls when their computed text size is too small. The observed threshold is approximately 16 CSS pixels.

Use:

:where(
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  select,
  textarea
) {
  font: inherit;
  font-size: max(1rem, 16px);
}

Keep labels and help text smaller if needed. Make controls compact through padding and line-height, not tiny editable text.

Do not disable user zoom.

Do not treat text-size-adjust, touch-action, or inputmode as fixes for focus zoom.


22. Print CSS#

Print should transform an application into a document.

22.1 Visibility utilities#

.print-only {
  display: none;
}

@media print {
  .print-hidden,
  [data-print="exclude"] {
    display: none !important;
  }

  .print-only {
    display: block;
  }
}

Use display: none, not visibility: hidden, for content removed from print.

Do not use the HTML hidden attribute merely for medium-specific visibility.

22.2 Security#

CSS hiding is not redaction.

Sensitive values must be omitted or masked before they reach the client.

Use a dedicated print route or server-generated PDF for authoritative, regulated, or privacy-sensitive documents.

22.3 Print palette#

@media print {
  :root {
    print-color-adjust: economy;
    -webkit-print-color-adjust: economy;
  }

  body {
    color: #000;
    background: #fff;
  }

  .card {
    background: transparent;
    box-shadow: none;
    border: 0.5pt solid #888;
  }
}

Use exact selectively for small meaningful regions such as barcodes, QR codes, or essential chart keys.

Design for grayscale and photocopy degradation.

22.4 Print layout#

  • Flatten app shells.
  • Remove nested scrolling.
  • Reset sticky and fixed positioning.
  • Keep native tables.
  • Use bounded break-inside: avoid.
  • Prefer size: auto unless paper size is formal.
  • Do not rely on margin boxes for critical content.
  • Test Chrome, Firefox, Safari, PDF, and physical printing.

23. Modern CSS reset#

Use a small reset, not a total erasure of browser defaults.

@layer reset {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    tab-size: 4;
  }

  body {
    margin: 0;
  }

  :where(
    h1, h2, h3, h4, h5, h6,
    p, figure, blockquote, dl, dd
  ) {
    margin: 0;
  }

  :where(
    ul[role="list"],
    ol[role="list"]
  ) {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  :where(img, picture, video, canvas) {
    display: block;
    max-inline-size: 100%;
  }

  :where(img, video) {
    block-size: auto;
  }

  svg {
    max-inline-size: 100%;
  }

  :where(button, input, select, textarea) {
    font: inherit;
    color: inherit;
    letter-spacing: inherit;
  }

  ::file-selector-button {
    font: inherit;
  }
}

Do not put these in the reset:

  • appearance: none
  • Focus removal
  • Global all: unset
  • Global smooth scrolling
  • Global animation destruction
  • Font-smoothing directives
  • html { font-size: 62.5%; }
  • Universal text breaking

24. CSS starter architecture#

@layer
  reset,
  vendor,
  tokens,
  base,
  layout,
  components,
  utilities,
  overrides;

Suggested files:

styles/
├── index.css
├── reset.css
├── tokens.css
├── base.css
├── layout.css
├── components/
│   ├── button.css
│   ├── form-control.css
│   ├── dialog.css
│   └── table.css
└── utilities.css

Useful layout primitives:

  • .wrapper
  • .flow
  • .stack
  • .cluster
  • .auto-grid
  • .sidebar-layout
  • .scroll-region
  • .visually-hidden

Keep the primitive set small.


25. HTML and CSS comments#

Comments preserve information the code cannot reliably reveal:

  • Intent
  • Contract
  • Invariant
  • Ownership
  • Constraint
  • Validation
  • Exit condition

They should not narrate syntax.

Controlled vocabulary#

  • SECTION
  • COMPONENT
  • PURPOSE
  • CONTRACT
  • INVARIANT
  • STATE
  • A11Y
  • SECURITY
  • PERF
  • WORKAROUND
  • VALIDATE
  • TODO
  • GENERATED

Example:

/* COMPONENT: data-grid
 * OWNS: Sticky headers and selection presentation.
 * DOES NOT OWN: Selection state or persistence.
 * STATE OWNER: data-grid.ts
 * VALIDATE: pnpm test:data-grid && pnpm test:visual
 */

HTML comments delivered to the browser are public. Do not include secrets, internal URLs, security bypasses, or customer data.

For complex components, prefer a local README with:

  • Purpose
  • Semantic baseline
  • State ownership
  • File map
  • Validation commands
  • Relevant ADRs

Use one canonical component name across HTML, CSS, JavaScript, tests, and docs.


26. Security defaults#

Open in the same tab by default.

When a new tab is required:

<a
  href="https://external.example/"
  target="_blank"
  rel="noopener">
  External documentation
</a>

Use noreferrer only when referrer suppression is intentional.

Do not treat nofollow, ugc, sponsored, or external as security controls.

Forms and state changes#

  • Use POST or an appropriate mutation endpoint.
  • Validate and authorize on the server.
  • Use CSRF protection.
  • Use defensive cookie settings.
  • Confirm destructive actions.
  • Use idempotency protection where duplicate submission matters.

DOM updates#

Prefer:

element.textContent = userValue;
container.append(document.createElement("li"));

Avoid unsanitized innerHTML, eval(), and javascript: URLs.

For applications that must accept rich HTML:

  1. Prefer server-side sanitization and a constrained content model.
  2. Enforce Trusted Types through CSP where the browser floor permits.
  3. Centralize and review the smallest possible set of Trusted Type policies.
  4. Use the safe HTML Sanitizer API methods only after feature detection.
  5. Never fall back from a missing safe method to an unsafe insertion sink.

Trusted Types reduce the number of code paths allowed to write to DOM XSS sinks. They do not decide what content is safe; the policy or sanitizer still carries that responsibility.

Production controls#

  • Strict Content Security Policy
  • require-trusted-types-for 'script' and a restricted trusted-types allowlist for applications using HTML sinks
  • Self-hosted assets where practical
  • Subresource Integrity for immutable third-party CDN assets
  • Restricted iframes with title, sandbox, allow, loading, and referrerpolicy

27. Accessibility baseline#

Target WCAG 2.2 AA.

WCAG 3 remains an incomplete working draft. Monitor it for future planning, but do not use it as the current conformance target or replace WCAG 2.2 testing with draft requirements.

Default requirements:

  • Reflow at 320 CSS pixels
  • Visible focus
  • Focus not obscured by sticky content
  • Minimum 24×24 CSS-pixel targets; approximately 44×44 preferred
  • No keyboard traps
  • No hover-only content
  • No orientation lock unless essential
  • No drag-only operation
  • Descriptive headings and labels
  • Meaningful source order
  • Zoom remains enabled
  • Reduced motion is respected
  • Forced-colors support for critical states
  • Non-color cues for status and selection

28. Performance baseline#

Generated prototypes should:

  • Render meaningful server/static HTML.
  • Avoid a blank client root.
  • Include explicit image dimensions.
  • Use responsive images.
  • Avoid lazy-loading the likely LCP image.
  • Minimize third-party scripts.
  • Avoid hydrating static content.
  • Paginate large datasets.
  • Add virtualization only after measurement.
  • Update existing DOM instead of rebuilding large HTML strings.
  • Pause or reduce live refresh when hidden.

29. Portable design context and personalization#

The handbook defines semantic, behavioral, accessibility, security, performance, and compatibility guidance. It does not prescribe one visual identity.

Use DESIGN.md as the portable visual-context layer.

DESIGN.md should describe what the interface should look and feel like. It should not replace the actual component library, token source, implementation documentation, lint rules, or tests.

29.1 Precedence#

Apply decisions in this order:

  1. Legal, privacy, security, and server-authoritative requirements
  2. Accessibility and user-preference requirements
  3. Semantic HTML and resilient behavior from this handbook
  4. Product purpose, audience, content, and workflow
  5. Existing project components, tokens, and implementation standards
  6. Project DESIGN.md
  7. External visual references
  8. Starter-pack defaults

A visual reference may change color, type, spacing, shape, imagery, density, and component styling. It must not weaken semantics, keyboard behavior, focus visibility, contrast, responsive reflow, print output, security controls, or meaningful no-JavaScript content.

29.2 Context stack#

Use the smallest stack appropriate to the project.

Portable prototype
├── native-first-prototype-handbook.md
├── DESIGN.md
└── preview.html

Established project
├── AGENTS.md
├── native-first-prototype-handbook.md
├── DESIGN.md
├── design/
│   ├── components.md
│   ├── data-visualization.md
│   ├── content.md
│   └── accessibility.md
├── tokens/
├── component registry or Storybook
├── preview.html
└── lint and test configuration

Enterprise design system
├── All project context above
├── Existing component packages
├── Machine-readable component metadata
├── On-demand MCP, skill, or retrieval interface
├── Automated token and usage validation
└── Visual, accessibility, and browser test suites

The root DESIGN.md is a compact map and visual contract. Detailed component guidance should be loaded only when the component is relevant.

29.3 How the source ecosystem correlates#

Source Best role What to take What not to assume
Google design.md specification Emerging portable alpha format YAML front matter, rationale prose, lint/diff/export workflow A stable standard or complete production design-system implementation
Aura PaperFlow Visual and layout exemplar Modular paper-like composition, whitespace, restrained accent use, previewable DESIGN.md packaging Authoritative product semantics, exact tokens, or licensed assets
Refero Styles Analyzed visual-reference library Palette, typography, spacing, component language, design rationale Permission to clone source layouts, branding, copy, or proprietary fonts
Awesome DESIGN.md Discovery and comparative corpus Reusable document organization, do/don't patterns, preview conventions That extracted brand files are current, official, complete, or appropriate to copy
Atlassian DESIGN.md research Production operating guidance Portability boundaries, context budgeting, component reuse, on-demand guidance, lint enforcement That one internal benchmark generalizes to every model or project

Use visual libraries as evidence and inspiration. Use the project’s implementation, tests, and product requirements as authority.

29.4 DESIGN.md format#

Google's current format is version alpha and is under active development. Pin the CLI version used by the project, record the review date, and rerun linting when intentionally upgrading.

A portable DESIGN.md combines:

  1. YAML front matter containing compact machine-readable values
  2. Markdown prose explaining the design intent, restrictions, and application
---
name: Project visual system
description: Warm, information-dense operational interface
colors:
  canvas: "#F7F5F0"
  ink: "#181714"
  accent: "#C95F32"
spacing:
  sm: 0.5rem
  md: 1rem
rounded:
  sm: 0.375rem
  md: 0.75rem
---

## Overview

A calm operational workspace that feels like a well-edited technical
notebook rather than a generic SaaS dashboard.

Token values provide precision. Prose provides the reasoning needed to apply those values coherently.

The current alpha schema recognizes these top-level token groups:

  • colors
  • typography
  • rounded
  • spacing
  • components

Typography dimensions must be simple dimensions such as 4rem; complex implementation expressions such as clamp() belong in prose or generated CSS. Component token properties are currently limited to backgroundColor, textColor, typography, rounded, padding, size, height, and width. Put borders, focus behavior, responsive logic, accessibility rules, and other richer semantics in prose, project tokens, or an implementation-specific sidecar.

29.5 Specific references beat generic adjectives#

Avoid:

Modern, clean, premium, trustworthy.

Prefer:

A field engineer's technical notebook translated into an operational web
application: warm paper, dark ink, compact annotations, strong table rules,
and one restrained signal accent.

A specific material, publication, instrument, environment, or historical reference gives the generator constraints as well as visual direction.

29.6 Normalize references into semantic roles#

Do not bind components directly to source-specific names.

Translate references into semantic roles:

@layer tokens {
  :root {
    --surface-canvas: #f8f8f6;
    --surface-raised: #ffffff;
    --surface-subtle: #efeeeb;

    --text-primary: #121212;
    --text-secondary: #373734;
    --text-muted: #6f6d68;

    --border-subtle: #d7d5cf;
    --accent-decorative: #d97757;

    --action-primary-background: #121212;
    --action-primary-foreground: #f8f8f6;

    --focus-ring: #0b63ce;
    --status-info: #225ea8;
    --status-success: #176c3a;
    --status-warning: #8a5200;
    --status-danger: #a12b2b;
  }
}

An external reference may use one decorative accent. The application may still require distinct focus, error, warning, success, and information roles.

29.7 Adapt, do not clone#

For each material reference, record:

Decision Meaning
Keep Preserve the principle directly
Adapt Translate it for product, accessibility, or platform needs
Reject Do not use because it conflicts with requirements
Unknown Validate before using

Example:

Reference characteristic Decision Application
Warm off-white canvas Keep Default light canvas
Modular paper-like sections Adapt Product-specific semantic modules
Small interface typography Adapt Preserve density while keeping editable controls approximately 16px or larger on iPhone
One orange or clay accent Adapt Decorative accent plus separate accessible functional colors
Minimal shadows Keep Prefer borders and surface contrast
Proprietary font Reject unless licensed Use a documented licensed or system fallback
Exact source layout or brand mark Reject Generate product-specific composition

29.8 Root context must remain concise#

A single portable file is loaded all at once in many tools. As it grows, it increases latency, token consumption, context competition, and the risk that relevant details are truncated.

Keep the root file focused on:

  • Visual intent
  • Foundational roles
  • High-value constraints
  • Common component treatments
  • Do's and don'ts
  • References to deeper authoritative context

Move detailed material to:

design/
├── components/
│   ├── button.md
│   ├── data-grid.md
│   └── dialog.md
├── patterns/
│   ├── dashboard.md
│   └── forms.md
└── foundations/
    ├── accessibility.md
    └── data-visualization.md

Use on-demand retrieval, skills, or MCP tools when the design system is too large to load efficiently as one document.

29.9 Existing components take precedence#

In an established application, DESIGN.md should tell agents to use the existing implementation rather than recreate it.

## Technical integration

Use the existing component package.

- Button: `@project/ui/button`
- Dialog: `@project/ui/dialog`
- Data table: `@project/ui/data-table`

Do not reimplement these components from token descriptions.
See `design/components.md` and Storybook for supported properties.

The visual file captures intent. The component library captures tested behavior and implementation.

Lint rules and tests should enforce:

  • Approved components
  • Deprecated token avoidance
  • Accessible patterns
  • Browser-support policy
  • Styling boundaries
  • No-JavaScript baseline requirements

29.10 Reference provenance and confidentiality#

Record for each reference:

  • Source URL or file
  • Date reviewed
  • Official, community, or inferred status
  • What was observed
  • What was adapted
  • Licensing or trademark constraints
  • Whether it can be included in public prompts

Do not publish a portable design file containing:

  • Confidential design-system internals
  • Private component names
  • Non-public roadmap information
  • Proprietary asset URLs
  • Customer-specific branding without authorization
  • Security-relevant implementation details

Maintain a sanitized public or customer-uploadable variant when required.

29.11 Preview artifacts#

A design-context package should include a no-JavaScript visual fixture.

Recommended files:

DESIGN.md
preview.html
preview-dark.html       # when dark mode exists
preview-print.html      # when print is a product feature
reference-sources.md

The preview should demonstrate:

  • Color roles
  • Typography hierarchy
  • Spacing
  • Buttons
  • Form controls
  • Navigation
  • Cards and sections
  • Tables and data states
  • Dialog/disclosure styling
  • Focus and invalid states
  • Long content
  • Print behavior

The preview is a validation fixture, not a marketing page.

29.12 Machine validation#

Where compatible with the project, validate and compare portable design files. Pin the reviewed package version and use the cross-platform designmd binary:

npx -p @google/design.md@0.2.0 designmd lint DESIGN.md
npx -p @google/design.md@0.2.0 designmd diff DESIGN.md DESIGN.next.md

The package remains alpha. Upgrade the pinned version deliberately and review schema, lint, export, and generated-output changes.

Also validate:

  • WCAG contrast
  • Font and asset licensing
  • Broken token references
  • Existing component import guidance
  • Narrow and wide layouts
  • JavaScript-disabled output
  • Keyboard and focus behavior
  • Reduced motion
  • Forced colors
  • Right-to-left text
  • iPhone form focus
  • Print and grayscale
  • Visual regressions

29.13 Token interchange#

When structured token exchange is required, prefer a format aligned with the Design Tokens Community Group specification.

The DTCG Resolver Module adds contextual token values for themes, size classes, high-contrast modes, and related conditions. It is a Community Group Candidate Recommendation, not a W3C Recommendation. Use it as an interoperability contract only when the participating tools implement the same revision.

Keep contextual token data in a tokens.json sidecar when it exceeds the current DESIGN.md alpha schema.

Recommended flow:

External reference or design library
  → observed or exported primitive tokens
  → portable DESIGN.md context
  → optional DTCG tokens.json for modes and contexts
  → semantic project tokens
  → component tokens
  → existing components or HTML/CSS implementation

Do not bind application components directly to raw reference values when a semantic role can be used.

29.14 Prompting future generation#

Attach or reference:

  1. resilient-mobile-first-web-handbook.md
  2. Project DESIGN.md
  3. Relevant component guidance or registry, when available

Recommended instruction:

Use the handbook as behavioral and implementation guidance.
Use DESIGN.md as the portable visual-intent layer.

Use existing project components when they are available. Do not recreate
them from visual tokens. Load detailed component guidance only for the
components involved in this task.

Translate external references into semantic project tokens and
product-specific composition. Do not copy source branding, layouts, copy,
fonts, or assets.

Accessibility, security, compatibility, product requirements, print
requirements, and meaningful no-JavaScript content take precedence.

29.15 Validation questions#

Before accepting personalized output, ask:

  • Does this feel intentionally specific rather than generically polished?
  • Can the design be explained without listing only adjectives?
  • Does it use existing components where available?
  • Is important guidance in the root file, and detailed guidance retrievable on demand?
  • Are source, licensing, and inference boundaries documented?
  • Does the result remain usable without JavaScript?
  • Does it pass accessibility, browser, security, and print checks?
  • Is it product-specific rather than a clone of the reference?

30. Enterprise design review rubric#

Score each category from 0 to 5 and apply the suggested weight.

Category Weight Review question
Task completion 20% Can users complete the primary outcome quickly and correctly?
Discoverability 15% Are destination, actions, state, and next steps apparent?
Accessibility 15% Can users operate every workflow across input and assistive modes?
Performance and resilience 15% Does the application remain useful under latency and partial failure?
Content clarity 10% Is information understandable without organizational knowledge?
Trust and responsibility 10% Are data use, consequences, permissions, and automation transparent?
Responsive adaptation 10% Does each context receive an appropriate composition?
Visual craftsmanship 5% Are hierarchy, typography, spacing, states, and motion coherent?

30.1 Review gates#

A release should fail review when:

  • The primary path is blank or inaccessible without JavaScript.
  • A critical action has no keyboard or non-drag alternative.
  • Focus is hidden or obscured.
  • Important status relies only on color.
  • User work is discarded after recoverable failure.
  • Mobile content is merely a scaled-down desktop surface.
  • A data comparison is made materially harder by converting it to cards.
  • Authorization or security is represented only in client state.
  • Print hides sensitive content instead of securely excluding it.
  • Experimental features own critical functionality without fallback.

30.2 Production-readiness state checklist#

Verify:

  • Primary task is obvious.
  • Navigation is discoverable.
  • Touch targets are adequate.
  • Keyboard workflows are complete.
  • Screen-reader semantics are verified.
  • Zoom and reflow work.
  • Loading, empty, stale, offline, failed, unauthorized, and success states exist.
  • Core paths survive slow or failed JavaScript.
  • History and deep links preserve relevant state.
  • Forms preserve valid user work.
  • Privacy and consent are explicit.
  • Performance budgets are measured in real use.
  • Print and export paths are tested.
  • Browser support is documented.
  • Source references and design provenance are documented.

31. Prototype generation checklist#

HTML#

  • <!doctype html>
  • lang
  • charset
  • viewport meta
  • descriptive title
  • skip link
  • semantic landmarks
  • real links and forms
  • useful content before JavaScript
  • no blank hydration shell

CSS#

  • small reset
  • explicit cascade layers
  • narrow layout first
  • intrinsic sizing
  • logical properties
  • visible :focus-visible
  • min-inline-size: 0
  • reduced-motion handling
  • print styles
  • no unnecessary experimental dependency

Forms#

  • visible labels
  • grouped questions use fieldset and legend
  • correct native input type
  • file-upload forms use multipart/form-data
  • file limits and accepted types are stated
  • server-side upload validation and scanning are defined
  • correct input types
  • autocomplete
  • native constraints
  • server validation
  • editable text at least approximately 16px on iOS
  • no disabled user zoom

JavaScript#

  • clear justification
  • enhancement over baseline
  • small ES modules
  • no inline handlers
  • safe DOM APIs
  • focus management where required
  • meaningful live announcements
  • no permanent visual-only ordering state

Data#

  • table semantics retained
  • caption and headers
  • labeled scroll region when needed
  • server pagination before virtualization
  • complete print/export path

Drag and drop#

  • correct spatial model
  • canonical data state
  • dedicated handle
  • keyboard alternative
  • non-dragging pointer alternative
  • focus restoration
  • live-region result announcement
  • server validation of persisted layout

Security#

  • rel="noopener" for new tabs
  • no secrets in HTML, comments, or data-*
  • server authorization
  • CSRF protection
  • CSP
  • no unsanitized HTML insertion
  • iframe restrictions
  • print hiding not treated as redaction

Validation#

  • JavaScript disabled
  • keyboard only
  • narrow viewport
  • browser zoom
  • iOS Safari
  • reduced motion
  • forced colors
  • right-to-left
  • print preview
  • physical or PDF print
  • automated accessibility tests
  • visual regression tests

32. Takeover and update workflow#

This file is the canonical source.

When new learning changes guidance:

  1. Update this Markdown file.
  2. Change the “Last reviewed” date.
  3. Add or update references.
  4. Regenerate the HTML companion.
  5. Note significant policy changes in a short changelog.
  6. Update code templates or examples that depend on the changed rule.

Recommended repository placement:

docs/
├── native-first-prototype-handbook.md
├── native-first-prototype-handbook.html
└── adr/

Recommended instruction in AGENTS.md:

Before generating or modifying prototype HTML/CSS, read:
`docs/native-first-prototype-handbook.md`.

Treat it as guidance, not a rigid framework. Preserve meaningful
no-JavaScript content unless the feature inherently requires scripting.

33. Reference index#

Use primary sources where possible.

Current platform status#

Enterprise and mobile design systems#

HTML, CSS, and accessibility#

Compatibility and web platform#

Security#

Testing#

Portable design context and design systems#

CSS architecture and resets#

Drag and layout systems#


34. Changelog#

2026-07-14#

  • Added scoped-chapter sticky-heading guidance after an iPhone defect showed multiline sticky siblings stacking and an inaccurate primary-header offset producing a visible gap.
  • Added horizontal-overflow regression guidance, shrinkable sticky-header rules, and cross-browser disclosure-marker alignment guidance after an iPhone preview defect.
  • Added a native HTML element coverage and browser file-I/O chapter covering file uploads, File API enhancement, output/progress/meter, field grouping, media, semantic text, tables, embeds, templates, and emerging elements.
  • Consolidated the separate mobile-first enterprise handbook into the canonical guide.
  • Replaced default dialog-opening JavaScript with declarative Invoker Commands and a conditional legacy shim.
  • Corrected the Interop 2025/Baseline timeline for View Transitions, @scope, anchor positioning, and Navigation API.
  • Added current-evergreen and broad-resilient compatibility profiles.
  • Added mobile decision surfaces, enterprise content priority, state completeness, responsible AI, internationalization, and a weighted enterprise review rubric.
  • Red-teamed the handbook against Interop and Baseline 2026, WCAG 3 status, Trusted Types, Declarative Shadow DOM, and the current Google DESIGN.md alpha schema.
  • Fixed duplicate handbook structure and corrected the DESIGN.md template so it passes the pinned CLI without errors.
  • Replaced the initial design-profile guidance with a portable DESIGN.md context stack informed by the Google format, Aura and Refero references, the Awesome DESIGN.md corpus, and Atlassian's production findings.
  • Added a design-reference and DESIGN.md personalization layer, including a Refero-derived example and semantic-token adaptation rules.
  • Initial reconciliation of native-first HTML/CSS guidance.
  • Added mobile-first layout, progressive disclosure, data grids, drag/drop, print, reset, comments, security, and validation standards.
  • Established Markdown as the canonical source and HTML as a derived companion.