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:
- Semantic HTML
- CSS layout and presentation
- Native browser behavior
- Server navigation and form submission
- Small JavaScript enhancement
- 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:
- Where am I?
- What matters now?
- What can I do next?
- 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.
Privacy and consent#
- 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-modalcloserequest-closeshow-popoverhide-popovertoggle-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:
- Keep the declarative attributes in the HTML.
- Feature-detect command support.
- Load a small fallback only when missing.
- 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:
autofor ordinary light-dismiss popoversmanualfor independently controlled persistent popovershintonly as progressive enhancement for tooltip-like content that should coexist with an openautopopover
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
multipleonly when several files are part of the task. - Use
acceptas 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.uploadexposes 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 |
|---|---|
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
inputmodeto improve the keyboard without misrepresenting value semantics. - Use
enterkeyhintwhen 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 identifierslangon content that changes languagedir="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
playsinlinefor embedded mobile playback. - Use
preload="metadata"ornoneunless 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
allowpermissions policy referrerpolicyloading- 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:
- Does the interaction require business state?
- Must state survive navigation or reload?
- Must data be fetched without page navigation?
- Must state changes be announced dynamically?
- Does the widget require managed focus?
- Does it require arrow-key navigation or roving
tabindex? - Does it require asynchronous search or options?
- Does it coordinate several independent components?
- Does the target browser floor lack required native behavior?
- Would the fallback hide essential information?
- Does the action require rollback or error recovery?
- 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
100vwinside 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
startandend. - Use
gapinstead of child margins. - Use baseline alignment for mixed typography.
- Use
safe centerwhere centering could clip. - Avoid
space-betweenfor unstable toolbars. - Avoid visual reordering that differs from DOM order.
- Treat
stretchas 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
currentColorso 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:
- Remove the secondary sticky layer at that breakpoint.
- Keep the primary header to a guaranteed single-line compact composition.
- Measure the header with
ResizeObserverand update a CSS custom property. - 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:
- Remove redundant columns.
- Shorten labels without losing meaning.
- Allow wrapping.
- Use a labeled scroll region.
- Add filtering and server pagination.
- Add a record-detail view.
- 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: noneto 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:
- Native state:
open,checked,disabled,invalid - Semantic ARIA state
- Component
data-*state - Ancestor-derived state with
:has() - Downward propagation through custom properties
- 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
@scopewith 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() @propertycontrast-color()for constrained palettes only- CSS Custom Highlight API
field-sizing: content- Declarative Shadow DOM
content-visibility- Scroll snap
scrollbar-gutterhidden="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 verifiedpopover="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-floworreading-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.
@scopeand 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: autounless 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#
SECTIONCOMPONENTPURPOSECONTRACTINVARIANTSTATEA11YSECURITYPERFWORKAROUNDVALIDATETODOGENERATED
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#
Links#
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:
- Prefer server-side sanitization and a constrained content model.
- Enforce Trusted Types through CSP where the browser floor permits.
- Centralize and review the smallest possible set of Trusted Type policies.
- Use the safe HTML Sanitizer API methods only after feature detection.
- 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 restrictedtrusted-typesallowlist 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, andreferrerpolicy
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:
- Legal, privacy, security, and server-authoritative requirements
- Accessibility and user-preference requirements
- Semantic HTML and resilient behavior from this handbook
- Product purpose, audience, content, and workflow
- Existing project components, tokens, and implementation standards
- Project
DESIGN.md - External visual references
- 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:
- YAML front matter containing compact machine-readable values
- 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:
colorstypographyroundedspacingcomponents
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:
resilient-mobile-first-web-handbook.md- Project
DESIGN.md - 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
fieldsetandlegend - 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:
- Update this Markdown file.
- Change the “Last reviewed” date.
- Add or update references.
- Regenerate the HTML companion.
- Note significant policy changes in a short changelog.
- 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#
- Invoker Commands API: https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API
- Baseline 2025: https://web.dev/baseline/2025
- Baseline 2026: https://web.dev/baseline/2026
- Interop 2025: https://web.dev/blog/interop-2025
- Interop 2026: https://web.dev/blog/interop-2026
@scope: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@scope- Navigation API: https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API
- Same-document View Transitions: https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition
- CSS anchor positioning: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Anchor_positioning
field-sizing: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/field-sizing<search>: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/search- File input: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/file
- File API: https://developer.mozilla.org/en-US/docs/Web/API/File_API
- File System API: https://developer.mozilla.org/en-US/docs/Web/API/File_System_API
- Output element: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/output
- Progress element: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/progress
- Meter element: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meter
- Fieldset: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/fieldset
- Media tracks: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/track
- File Upload Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html
inert: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inerthidden="until-found": https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/hiddenElement.moveBefore(): https://developer.mozilla.org/en-US/docs/Web/API/Element/moveBefore
Enterprise and mobile design systems#
- GOV.UK Design System: https://design-system.service.gov.uk/
- U.S. Web Design System: https://designsystem.digital.gov/
- IBM Carbon: https://carbondesignsystem.com/
- Microsoft Fluent 2: https://fluent2.microsoft.design/
- Material Design 3: https://m3.material.io/
- GitHub Primer: https://primer.style/
- Atlassian Design System: https://atlassian.design/
- Salesforce Lightning Design System: https://www.lightningdesignsystem.com/
- W3C Ethical Web Principles: https://www.w3.org/TR/ethical-web-principles/
- OWASP ASVS: https://owasp.org/www-project-application-security-verification-standard/
HTML, CSS, and accessibility#
- MDN Web Docs: https://developer.mozilla.org/
- W3C Web Accessibility Initiative: https://www.w3.org/WAI/
- ARIA Authoring Practices Guide: https://www.w3.org/WAI/ARIA/apg/
- WCAG 2.2: https://www.w3.org/TR/WCAG22/
Compatibility and web platform#
- Web Platform Baseline: https://web.dev/baseline
- Interop 2026: https://web.dev/blog/interop-2026
- Baseline 2026 feature list: https://web.dev/baseline/2026
- Web Platform Status: https://webstatus.dev/
- Can I Use: https://caniuse.com/
- WebKit Blog: https://webkit.org/blog/
- Chrome for Developers: https://developer.chrome.com/
Security#
- OWASP Cheat Sheet Series: https://cheatsheetseries.owasp.org/
- MDN Web Security: https://developer.mozilla.org/en-US/docs/Web/Security
- Trusted Types API: https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API
- HTML Sanitizer API: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Sanitizer_API
Testing#
- Playwright: https://playwright.dev/
- axe-core: https://github.com/dequelabs/axe-core
- Stylelint: https://stylelint.io/
Portable design context and design systems#
- Google DESIGN.md specification: https://github.com/google-labs-code/design.md
- Google DESIGN.md philosophy: https://github.com/google-labs-code/design.md/blob/main/PHILOSOPHY.md
- Stitch DESIGN.md overview: https://stitch.withgoogle.com/docs/design-md/overview
- Atlassian DESIGN.md production research: https://www.atlassian.com/blog/how-we-build/atlassians-design-md-is-here-what-we-learned-testing-portable-design-context-in-practice
- Awesome DESIGN.md collection: https://github.com/VoltAgent/awesome-design-md/
- Aura PaperFlow visual/layout reference: https://www.aura.build/design-systems/paperflow-design-layout
- Refero Styles library: https://styles.refero.design/
- Refero Claude style reference: https://styles.refero.design/style/47cb86b6-cb2d-41c8-94ba-8607cd7c41cd
- Refero DESIGN.md examples: https://styles.refero.design/ai-agents/design-md-examples
- Design Tokens Community Group reports: https://www.designtokens.org/TR/2025.10/
- Design Tokens format specification: https://www.designtokens.org/TR/2025.10/format/
CSS architecture and resets#
- Normalize.css: https://github.com/necolas/normalize.css/
- modern-normalize: https://github.com/sindresorhus/modern-normalize
- A Modern CSS Reset: https://piccalil.li/blog/a-more-modern-css-reset/
- Josh Comeau CSS Reset: https://www.joshwcomeau.com/css/custom-css-reset/
Drag and layout systems#
- Pointer Events: https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events
- HTML Drag and Drop: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
- Dnd Kit: https://github.com/clauderic/dnd-kit
- SortableJS: https://github.com/SortableJS/Sortable
- Interact.js: https://interactjs.io/
- GridStack: https://gridstackjs.com/
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.mdcontext 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.mdpersonalization 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.