From 5a120fff5fa47e4929b7a55a1139aad251cad733 Mon Sep 17 00:00:00 2001 From: Micah Jo Date: Wed, 13 May 2026 21:21:29 -0400 Subject: [PATCH] updating discipline guides with skill --- .../skills/project-discipline-guides/SKILL.md | 4 +- .../mirrors/accessibility/claude_mirror.md | 595 ++ .../mirrors/accessibility/codex_mirror.md | 4900 +++++++++++++++++ .../mirrors/accessibility/gemini_mirror.md | 99 + .../mirrors/accessibility/mirror.md | 92 + .../mirrors/css/claude_mirror.md | 912 +++ .../mirrors/css/codex_mirror.md | 3844 +++++++++++++ .../mirrors/css/gemini_mirror.md | 74 + .../mirrors/css/mirror.md | 64 + .../mirrors/forms/claude_mirror.md | 692 +++ .../mirrors/forms/codex_mirror.md | 2792 ++++++++++ .../mirrors/forms/gemini_mirror.md | 115 + .../mirrors/forms/mirror.md | 114 + .../mirrors/html/claude_mirror.md | 503 ++ .../mirrors/html/codex_mirror.md | 1135 ++++ .../mirrors/html/gemini_mirror.md | 118 + .../mirrors/html/mirror.md | 118 + .../mirrors/javascript/claude_mirror.md | 842 --- .../mirrors/javascript/codex_mirror.md | 1941 ------- .../mirrors/javascript/gemini_mirror.md | 91 - .../mirrors/javascript/mirror.md | 248 - .../mirrors/performance/claude_mirror.md | 509 ++ .../mirrors/performance/codex_mirror.md | 2420 ++++++++ .../mirrors/performance/gemini_mirror.md | 105 + .../mirrors/performance/mirror.md | 89 + .../mirrors/security/claude_mirror.md | 666 +++ .../mirrors/security/codex_mirror.md | 1942 +++++++ .../mirrors/security/gemini_mirror.md | 138 + .../mirrors/security/mirror.md | 104 + .../mirrors/webmcp/claude_mirror.md | 493 ++ .../mirrors/webmcp/codex_mirror.md | 3188 +++++++++++ .../mirrors/webmcp/gemini_mirror.md | 108 + .../mirrors/webmcp/mirror.md | 68 + guides/accessibility/accessibility/guide.md | 21 - guides/css/css/guide.md | 18 +- guides/forms/forms/guide.md | 22 - guides/html/html/guide.md | 24 +- guides/performance/performance/guide.md | 8 - guides/security/security/guide.md | 48 +- 39 files changed, 26007 insertions(+), 3257 deletions(-) create mode 100644 .agents/skills/project-discipline-guides/mirrors/accessibility/claude_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/accessibility/codex_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/accessibility/gemini_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/accessibility/mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/css/claude_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/css/codex_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/css/gemini_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/css/mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/forms/claude_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/forms/codex_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/forms/gemini_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/forms/mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/html/claude_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/html/codex_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/html/gemini_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/html/mirror.md delete mode 100644 .agents/skills/project-discipline-guides/mirrors/javascript/claude_mirror.md delete mode 100644 .agents/skills/project-discipline-guides/mirrors/javascript/codex_mirror.md delete mode 100644 .agents/skills/project-discipline-guides/mirrors/javascript/gemini_mirror.md delete mode 100644 .agents/skills/project-discipline-guides/mirrors/javascript/mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/performance/claude_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/performance/codex_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/performance/gemini_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/performance/mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/security/claude_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/security/codex_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/security/gemini_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/security/mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/webmcp/claude_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/webmcp/codex_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/webmcp/gemini_mirror.md create mode 100644 .agents/skills/project-discipline-guides/mirrors/webmcp/mirror.md diff --git a/.agents/skills/project-discipline-guides/SKILL.md b/.agents/skills/project-discipline-guides/SKILL.md index 0fc7c0b22..9ab62c55e 100644 --- a/.agents/skills/project-discipline-guides/SKILL.md +++ b/.agents/skills/project-discipline-guides/SKILL.md @@ -1,11 +1,11 @@ --- name: project-discipline-guides -description: Workflow for refactoring discipline-level guides (e.g., JavaScript, CSS) to remove "Common Knowledge" by generating and comparing against model-specific "Knowledge Mirrors". +description: Workflow for refactoring discipline-level guides (e.g., performance, CSS) to remove "Common Knowledge" by generating and comparing against model-specific "Knowledge Mirrors". --- # Project Discipline Guides -This skill facilitates the "Differential Knowledge Refactor" of technical guides. It ensures that `SKILL.md` files for specific disciplines are lean, context-efficient, and strictly additive to what modern AI models already know natively. +This skill facilitates the "Differential Knowledge Refactor" of technical guides. It ensures that `guide.md` files for specific disciplines are lean, context-efficient, and strictly additive to what modern AI models already know natively. ## Workflow: The Inverse Knowledge Filter diff --git a/.agents/skills/project-discipline-guides/mirrors/accessibility/claude_mirror.md b/.agents/skills/project-discipline-guides/mirrors/accessibility/claude_mirror.md new file mode 100644 index 000000000..499498d21 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/accessibility/claude_mirror.md @@ -0,0 +1,595 @@ +# Accessibility Development: Common Knowledge Guide + +A comprehensive reference of standard accessibility best practices, syntax, and APIs that I apply by default when writing or reviewing accessible web content. + +--- + +## 1. Semantic HTML Foundations + +### Document Structure +- Always declare `` and set `` (or appropriate BCP 47 language tag). +- Use `` on the root and `lang` attributes on inline elements with different languages (e.g., `bonjour`). +- Set `dir="rtl"` for right-to-left languages; prefer `dir="auto"` for user-generated content. +- Provide a meaningful `` per page; update it for SPA route changes. +- Use `<meta name="viewport" content="width=device-width, initial-scale=1">` — never set `user-scalable=no` or `maximum-scale` below 5 (blocks zoom). + +### Landmarks +Use semantic landmark elements rather than `<div role="...">`: +- `<header>` (page banner when top-level) +- `<nav>` (navigation regions; label multiple with `aria-label`) +- `<main>` (one per page; primary content) +- `<aside>` (complementary content) +- `<footer>` (contentinfo when top-level) +- `<section>` (only when it has an accessible name via `aria-labelledby` or `aria-label`) +- `<article>` (self-contained content) +- `<search>` (new HTML element for search regions; replaces `role="search"`) + +### Headings +- Use one `<h1>` per page (typically), with logical hierarchy (`h1` → `h2` → `h3`). +- Don't skip levels for visual styling — use CSS to style. +- Headings define document outline used by screen reader rotor navigation. + +### Lists +- `<ul>`, `<ol>`, `<dl>` for grouped items. Don't fake lists with `<br>`. +- Don't apply `list-style: none` without considering Safari's removal of list semantics — restore with `role="list"` when needed for accessibility. + +### Text Content +- `<strong>` for importance, `<em>` for emphasis (not `<b>`/`<i>` for semantics). +- `<abbr title="...">` for abbreviations. +- `<time datetime="2026-05-13">` for machine-readable dates. +- `<address>` for contact info. +- `<blockquote cite="...">` and `<cite>` for citations. +- `<code>`, `<pre>`, `<kbd>`, `<samp>`, `<var>` for technical content. + +--- + +## 2. Forms + +### Labels (mandatory for every input) +Three valid patterns: +```html +<!-- Explicit (preferred) --> +<label for="email">Email</label> +<input id="email" type="email"> + +<!-- Implicit wrapping --> +<label>Email <input type="email"></label> + +<!-- aria-label / aria-labelledby when visual label not possible --> +<input type="search" aria-label="Search products"> +``` + +- Never use `placeholder` as the only label — disappears on input, low contrast, not announced reliably. +- Group related fields with `<fieldset>` and `<legend>` (e.g., radio groups, address blocks). + +### Input Types & Attributes +Use specific input types for keyboard & assistive tech support: +- `type="email"`, `type="tel"`, `type="url"`, `type="number"`, `type="search"`, `type="date"`, `type="time"`, `type="color"` +- `inputmode="numeric"` / `"decimal"` / `"tel"` / `"email"` for virtual keyboard hints (without changing validation semantics) +- `autocomplete="email"`, `"name"`, `"current-password"`, `"new-password"`, `"one-time-code"`, `"street-address"`, etc. — critical for cognitive accessibility and password managers +- `required`, `min`, `max`, `pattern`, `minlength`, `maxlength` for native validation +- `enterkeyhint="search"` / `"send"` / `"go"` / `"done"` for virtual keyboard return key + +### Errors & Validation +- Associate error messages with inputs via `aria-describedby`. +- Use `aria-invalid="true"` on invalid fields. +- Render errors in text, not just color. +- Prefer inline error messages near the field; provide a summary list at the top for long forms with focus management. +- Use `aria-live="polite"` regions for dynamic validation feedback. + +```html +<label for="pwd">Password</label> +<input id="pwd" type="password" aria-describedby="pwd-help pwd-err" + aria-invalid="true" autocomplete="new-password" required> +<p id="pwd-help">At least 8 characters.</p> +<p id="pwd-err" role="alert">Password is too short.</p> +``` + +### Buttons +- Use `<button type="button">` for actions, `<button type="submit">` (or rely on default) for submission. +- Always set explicit `type` to avoid accidental form submits. +- Never use `<div onclick>` — not focusable, not keyboard operable, no role. +- Disabled buttons (`disabled`) are removed from tab order and not announced as actionable; consider `aria-disabled="true"` if you need keyboard focus to remain (e.g., to surface why it's disabled). + +--- + +## 3. ARIA — Rules and Common Patterns + +### The Five Rules of ARIA +1. **Don't use ARIA if a native HTML element/attribute provides the semantics.** `<button>` > `<div role="button">`. +2. **Don't change native semantics** unless necessary (avoid `<h1 role="button">`). +3. **All interactive ARIA roles must be keyboard accessible.** +4. **Don't use `role="presentation"` or `aria-hidden="true"` on focusable elements.** +5. **All interactive elements must have an accessible name.** + +### Naming & Description +- `aria-label="..."` — overrides visible text; use when no visible label exists. +- `aria-labelledby="id1 id2"` — references visible text (multiple IDs concatenated). +- `aria-describedby="id"` — supplemental description, announced after the name. +- Accessible name calculation precedence: `aria-labelledby` > `aria-label` > native (e.g., `<label>`, alt, title) > content. + +### State & Properties +- `aria-expanded="true|false"` on disclosure triggers (accordions, dropdowns, menus). +- `aria-pressed="true|false"` for toggle buttons. +- `aria-checked="true|false|mixed"` for custom checkboxes/radios. +- `aria-selected="true|false"` for tabs, options, grid cells. +- `aria-current="page|step|location|date|time|true"` for current item in a set. +- `aria-disabled="true"` for visually disabled but focusable controls. +- `aria-hidden="true"` to hide from AT (don't use on focusable elements). +- `aria-controls="id"` to associate a control with the element it controls. +- `aria-haspopup="menu|listbox|tree|grid|dialog"` for elements that open popups. + +### Live Regions +- `aria-live="polite"` — announces when idle (status updates). +- `aria-live="assertive"` — interrupts (errors, urgent alerts). +- `role="status"` ≈ `aria-live="polite"`. +- `role="alert"` ≈ `aria-live="assertive"`. +- `role="log"`, `role="timer"`, `role="marquee"` for specialized live regions. +- `aria-atomic="true"` — re-read entire region on change. +- `aria-relevant="additions|removals|text|all"` — what changes to announce. +- Live region must exist in DOM **before** content is added; don't dynamically inject the region itself. +- `aria-busy="true"` while updates are in flight. + +### Common Roles +- `role="button"`, `"link"`, `"checkbox"`, `"radio"`, `"switch"` +- `role="dialog"`, `"alertdialog"` +- `role="tablist"`, `"tab"`, `"tabpanel"` +- `role="menu"`, `"menubar"`, `"menuitem"`, `"menuitemcheckbox"`, `"menuitemradio"` +- `role="listbox"`, `"option"`, `"combobox"` +- `role="tree"`, `"treeitem"`, `"treegrid"` +- `role="grid"`, `"row"`, `"gridcell"`, `"columnheader"`, `"rowheader"` +- `role="tooltip"` +- `role="progressbar"` (with `aria-valuenow`, `aria-valuemin`, `aria-valuemax`, `aria-valuetext`) +- `role="region"` (named landmark) + +--- + +## 4. Keyboard Accessibility + +### Focus Order & Tabbing +- Tab order must follow visual/reading order. Use DOM order; avoid positive `tabindex`. +- `tabindex="0"` — adds to natural tab order (use for custom interactive elements). +- `tabindex="-1"` — programmatically focusable but not in tab order (for focus management). +- Never use `tabindex` ≥ 1 (creates unpredictable order). + +### Focus Management +- After route changes in SPAs, move focus to the new page's heading or main container. +- After opening a modal, move focus inside (typically first focusable element or the dialog itself with `tabindex="-1"`); trap focus inside; return focus to the trigger on close. +- After deleting an item from a list, move focus to the next/previous item or a sensible neighbor. +- Use `element.focus({ preventScroll: true })` when scrolling would be jarring. +- Use `element.focus({ focusVisible: true })` to force focus ring (newer). + +### Focus Indicators +- Never `outline: none` without a replacement. +- Use `:focus-visible` to show indicators only for keyboard focus: +```css +:focus { outline: none; } +:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; } +``` +- Indicators must meet 3:1 contrast against adjacent colors (WCAG 2.4.11/2.4.13). + +### Keyboard Patterns (per WAI-ARIA Authoring Practices) +- **Buttons**: Enter and Space activate. +- **Links**: Enter activates. +- **Checkboxes/Switches**: Space toggles. +- **Radio groups**: Arrow keys move and select. +- **Tabs**: Arrow keys move between tabs (manual or automatic activation); Home/End jump to ends. +- **Listbox/Menu**: Arrows navigate, Enter/Space select, Escape closes, Type-ahead search by first letter. +- **Combobox**: Arrows open/navigate, Enter selects, Escape closes/clears. +- **Dialog**: Escape closes; focus trapped within. +- **Tree**: Arrow Right expands, Arrow Left collapses, Up/Down moves. +- **Slider**: Arrows adjust by step; Page Up/Down by larger step; Home/End to extremes. + +### Skip Links +```html +<a href="#main" class="skip-link">Skip to main content</a> +``` +Position off-screen; reveal on focus. + +--- + +## 5. Images & Media + +### Images +- `<img alt="Descriptive text">` — describe purpose/content concisely. +- `<img alt="">` — for decorative images (still required attribute). +- Don't start alt with "Image of" / "Picture of". +- For complex images (charts, diagrams), provide `alt` summary plus longer description nearby or via `aria-describedby`. +- SVG: use `<title>` (and optionally `<desc>`) inside, plus `role="img"` and `aria-labelledby`. Add `aria-hidden="true"` and `focusable="false"` for decorative inline SVG. +- `<figure>` + `<figcaption>` for captioned images. +- Icon-only buttons need `aria-label`. + +### Video & Audio +- Provide captions via `<track kind="captions" srclang="en" src="..." default>` for video. +- Provide transcripts for audio-only content. +- Provide audio descriptions (`kind="descriptions"`) when visual content conveys info not in the audio. +- Don't autoplay media with sound. If autoplay is unavoidable, start muted and provide controls. +- Always include `controls` attribute or accessible custom controls. +- Avoid content that flashes more than 3 times per second (seizure risk; WCAG 2.3.1). + +--- + +## 6. Color, Contrast & Visual Design + +### Contrast (WCAG 2.2 AA) +- Body text: 4.5:1 minimum against background. +- Large text (≥18pt or ≥14pt bold): 3:1. +- Non-text UI elements & graphical objects: 3:1 (icons, form borders, focus rings). +- AAA: 7:1 body / 4.5:1 large. +- WCAG 3 introduces APCA — perceptually-tuned contrast algorithm; not yet normative. + +### Color Use +- Don't rely on color alone to convey meaning (errors, required fields, chart series). Pair with text, icons, patterns, or shapes. +- Test in grayscale. + +### Reduced Motion +```css +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} +``` + +### Color Scheme +- Support `prefers-color-scheme: dark | light`. +- Set `color-scheme: light dark;` in CSS so form controls and scrollbars adapt. + +### Other Media Queries +- `prefers-contrast: more | less | custom` +- `prefers-reduced-transparency` +- `prefers-reduced-data` +- `forced-colors: active` — for Windows High Contrast Mode; use `system-color` keywords (`Canvas`, `CanvasText`, `LinkText`, `ButtonFace`, `ButtonText`, `Highlight`, etc.) and avoid removing borders. +- `inverted-colors` + +### Forced Colors Mode +- Don't override system colors blindly. +- Use `forced-color-adjust: none` sparingly when essential (e.g., color-coded charts). +- Test focus indicators remain visible (use `outline` rather than `box-shadow` since outlines respect forced colors). + +### Zoom & Reflow +- Content must reflow at 320 CSS pixels wide without horizontal scrolling (400% zoom on 1280px viewport). +- Text must scale to 200% without loss of content/functionality. +- Use relative units (`rem`, `em`, `%`, `ch`) over `px` for typography. +- Don't lock viewport zoom. + +### Spacing & Targets +- WCAG 2.5.8 (AA, 2.2): Interactive targets at least 24×24 CSS pixels. +- WCAG 2.5.5 (AAA): 44×44 CSS pixels. +- Sufficient spacing prevents accidental activation. +- Text spacing override (1.5.12): users must be able to override line-height to 1.5×, paragraph spacing to 2×, letter spacing to 0.12×, word spacing to 0.16× without loss. + +--- + +## 7. Tables + +```html +<table> + <caption>Quarterly revenue</caption> + <thead> + <tr><th scope="col">Quarter</th><th scope="col">Revenue</th></tr> + </thead> + <tbody> + <tr><th scope="row">Q1</th><td>$100k</td></tr> + </tbody> +</table> +``` +- Use `<th scope="col|row">` (and `scope="colgroup|rowgroup"` for spans). +- `<caption>` for the table's accessible name. +- Don't use tables for layout. +- Complex tables: `headers="id1 id2"` on cells referencing `<th id>`. + +--- + +## 8. Modern Native HTML APIs + +### `<dialog>` Element +```html +<dialog id="d"> + <form method="dialog"> + <button>Close</button> + </form> +</dialog> +<script> + d.showModal(); // modal with backdrop, focus trap, Escape to close + d.show(); // non-modal +</script> +``` +- `showModal()` provides automatic focus trap, inert background, Escape to close. +- Style backdrop with `dialog::backdrop`. +- `closedby="any|closerequest|none"` (newer) for declarative close behavior. + +### Popover API +```html +<button popovertarget="menu">Open</button> +<div id="menu" popover>...</div> +``` +- `popover` (auto), `popover="manual"`, `popover="hint"`. +- Light-dismiss, top layer, accessible by default. +- Pair with `popovertargetaction="show|hide|toggle"`. + +### `<details>` / `<summary>` +Native disclosure widget — keyboard accessible, exposes expanded state. Use `name` attribute (newer) for exclusive accordion groups. + +### `inert` Attribute +- `inert` on a subtree removes it from tab order, hides from AT, and disables pointer events. +- Use to handle background content when a modal is open (instead of manual `aria-hidden` + tabindex juggling). + +### `hidden` Attribute +- `hidden` (boolean) hides from rendering and AT. +- `hidden="until-found"` — hidden but findable via in-page find/scroll-to-text-fragment; auto-reveals. + +### Anchor Positioning (newer; progressive enhancement) +For tooltips/popovers using `anchor()` and `position-anchor`. + +--- + +## 9. JavaScript / DOM APIs + +### Focus +- `element.focus(options)` +- `document.activeElement` +- `element.matches(':focus-visible')` +- Roving tabindex pattern for composite widgets. + +### Observers for Dynamic UI +- `MutationObserver` — react to DOM changes (e.g., to update live region). +- `IntersectionObserver` — visibility changes. +- `ResizeObserver` — layout changes. + +### Page Visibility & Focus +- `document.visibilityState`, `visibilitychange` event. +- Pause animations / non-critical work when hidden. + +### Speech & Other APIs +- `SpeechSynthesis` API — supplemental TTS (not a replacement for proper semantics). +- `navigator.languages` — respect user language preferences. + +### Event Handling +- Don't rely solely on `mouseover`/`mouseout` — pair with `focus`/`blur` for keyboard. +- Use `pointerdown`/`pointerup` to support all input types. +- Don't `preventDefault()` on key events without preserving expected keyboard behavior. + +--- + +## 10. CSS Techniques + +### Visually Hidden (screen reader-only) +```css +.sr-only { + position: absolute; + width: 1px; height: 1px; + padding: 0; margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} +``` +Don't use `display: none` or `visibility: hidden` — they hide from screen readers too. + +### Logical Properties +Use logical properties for internationalization: +- `margin-inline-start` (not `margin-left`) +- `padding-block-end` (not `padding-bottom`) +- `inset-inline`, `border-inline-end`, `text-align: start` + +### Pseudo-classes +- `:focus-visible` — keyboard focus only. +- `:focus-within` — element or any descendant has focus. +- `:placeholder-shown`, `:user-invalid`, `:user-valid` (better than `:invalid` because they wait for user interaction). +- `:has()` — parent selector enabling many semantic patterns. + +### Don't Break Selection +- Avoid `user-select: none` on text content. + +### CSS-driven Order +- Don't use `flex-direction: row-reverse`, `order`, or `grid` placement to change visual order without considering keyboard/reading order divergence. + +--- + +## 11. Common Patterns + +### Modal Dialog Checklist +- `role="dialog"` (or `<dialog>`) with `aria-modal="true"`. +- Accessible name via `aria-labelledby` (heading) or `aria-label`. +- Focus moves to dialog on open. +- Focus trapped within while open. +- Escape closes. +- Focus returns to trigger on close. +- Background made `inert`. + +### Accordion / Disclosure +```html +<button aria-expanded="false" aria-controls="panel-1">Section</button> +<div id="panel-1" hidden>...</div> +``` +Or use `<details>`/`<summary>`. + +### Tabs +```html +<div role="tablist" aria-label="Settings"> + <button role="tab" aria-selected="true" aria-controls="p1" id="t1">One</button> + <button role="tab" aria-selected="false" aria-controls="p2" id="t2" tabindex="-1">Two</button> +</div> +<div role="tabpanel" id="p1" aria-labelledby="t1" tabindex="0">...</div> +<div role="tabpanel" id="p2" aria-labelledby="t2" tabindex="0" hidden>...</div> +``` +- Roving tabindex on tabs. +- Arrow keys navigate. + +### Combobox / Autocomplete +- `role="combobox"` on the input, `aria-expanded`, `aria-controls` to listbox, `aria-activedescendant` for highlighted option. +- Listbox with `role="listbox"`, options with `role="option"` and `aria-selected`. + +### Toast / Notification +- `role="status"` for non-critical, `role="alert"` for critical. +- Don't auto-dismiss critical messages without user action. + +### Loading States +- `aria-busy="true"` on the region being loaded. +- Announce completion with a live region. +- Provide visible spinner with accessible name (`role="progressbar"` with `aria-label`). + +### Tooltip +- Triggered on hover AND focus. +- Dismissible via Escape. +- Pointer can hover the tooltip without it disappearing (WCAG 1.4.13). +- Associate via `aria-describedby` (descriptive) or `aria-labelledby` (if it's the name). +- Don't put interactive content in tooltips. + +### Icon Buttons +```html +<button aria-label="Close"> + <svg aria-hidden="true" focusable="false">...</svg> +</button> +``` + +--- + +## 12. SPAs & Routing + +- On client-side route change: update `<title>`, move focus to the new view's heading/main, announce via live region if focus move isn't appropriate. +- Use real `<a href>` links for navigation, not `<div onclick>`. +- Preserve browser history; support back/forward. + +--- + +## 13. Internationalization Touchpoints + +- Don't construct sentences from concatenated translated fragments. +- Use ICU MessageFormat or i18n libraries for plurals/genders. +- Format numbers, dates, currencies with `Intl.NumberFormat`, `Intl.DateTimeFormat`, `Intl.RelativeTimeFormat`, `Intl.PluralRules`, `Intl.ListFormat`. +- Don't bake text into images. + +--- + +## 14. WCAG 2.2 Quick Reference + +### Perceivable +- 1.1.1 Non-text content (alt text) +- 1.2.x Time-based media (captions, transcripts, audio description) +- 1.3.1 Info and relationships (semantic markup) +- 1.3.5 Identify input purpose (autocomplete) +- 1.4.3 Contrast (minimum) +- 1.4.4 Resize text +- 1.4.10 Reflow +- 1.4.11 Non-text contrast +- 1.4.12 Text spacing +- 1.4.13 Content on hover or focus + +### Operable +- 2.1.1 Keyboard (all functionality) +- 2.1.2 No keyboard trap +- 2.1.4 Character key shortcuts (must be remappable) +- 2.2.1 Timing adjustable +- 2.2.2 Pause, stop, hide +- 2.3.1 Three flashes +- 2.4.1 Bypass blocks (skip links) +- 2.4.3 Focus order +- 2.4.4 Link purpose +- 2.4.6 Headings and labels +- 2.4.7 Focus visible +- 2.4.11 Focus not obscured (minimum) — new in 2.2 +- 2.5.3 Label in name (visible label text must appear in accessible name) +- 2.5.7 Dragging movements — new in 2.2 (must have single-pointer alternative) +- 2.5.8 Target size (minimum) — new in 2.2 + +### Understandable +- 3.1.1 Language of page +- 3.1.2 Language of parts +- 3.2.1 On focus (no context change) +- 3.2.2 On input (no surprise context change) +- 3.2.6 Consistent help — new in 2.2 +- 3.3.1 Error identification +- 3.3.2 Labels or instructions +- 3.3.3 Error suggestion +- 3.3.7 Redundant entry — new in 2.2 +- 3.3.8 Accessible authentication — new in 2.2 (no cognitive function tests for auth) + +### Robust +- 4.1.2 Name, role, value +- 4.1.3 Status messages + +--- + +## 15. Testing & Tooling + +### Automated Tools (catch ~30-40% of issues) +- axe-core / @axe-core/react / jest-axe +- Lighthouse accessibility audit +- WAVE +- Pa11y +- ESLint plugins: `eslint-plugin-jsx-a11y`, `eslint-plugin-vuejs-accessibility` + +### Manual Testing +- Tab through entire interface; verify focus visibility and logical order. +- Operate all controls with keyboard only. +- Zoom to 200% and 400%. +- Test with screen readers: + - **macOS/iOS**: VoiceOver (built-in) + - **Windows**: NVDA (free), JAWS + - **Android**: TalkBack + - **ChromeOS**: ChromeVox +- Test in forced colors / high contrast mode. +- Test with `prefers-reduced-motion`. +- Test with browser text-only zoom. +- Use accessibility tree inspector in DevTools. + +### Screen Reader Conventions +- VoiceOver: Ctrl+Option+arrow keys; rotor with VO+U. +- NVDA: Insert+Space toggles browse/focus mode; H for headings, F for form fields, K for links, R for landmarks, D for landmarks (NVDA). +- Different SR + browser combos behave differently — test the major pairs (NVDA+Firefox, NVDA+Chrome, JAWS+Chrome, VoiceOver+Safari). + +--- + +## 16. Common Anti-Patterns to Avoid + +- `<div>` / `<span>` with `onclick` for interactive controls. +- `aria-label` on non-interactive, non-landmark elements (often ignored). +- `role="button"` on `<a href>` (use the right element instead). +- Redundant ARIA: `<button role="button">`, `<nav role="navigation">`. +- `aria-hidden="true"` on focusable elements (creates "ghost" focus). +- Positive `tabindex` values. +- Removing focus outlines without replacement. +- Placeholder as label. +- Color-only error indication. +- Auto-playing media with sound. +- Carousels that auto-advance without controls. +- CAPTCHAs without accessible alternatives. +- Modals without focus management. +- Toast notifications that disappear before they can be read. +- "Click here" / "Read more" link text without context. +- Tooltips on touch-only interactions (no hover on touch). +- Trapping users in a widget without an escape (Escape key). +- Assuming pointer input (build for keyboard, mouse, touch, stylus, voice). +- Using `title` attribute as the only accessible name (inconsistent AT support, no touch access). +- Generic button labels like "Submit" without context. + +--- + +## 17. Cognitive Accessibility + +- Plain language; short sentences; common words. +- Consistent navigation and component placement across pages. +- Clear error recovery paths; never lose user input. +- Confirm destructive actions; allow undo where possible. +- Don't impose time limits without user control to extend/disable. +- Provide instructions and examples for complex inputs. +- Break long forms into steps with progress indicator. +- Use `autocomplete` to reduce memory burden. + +--- + +## 18. Mobile / Touch Accessibility + +- Support both portrait and landscape orientations (don't lock). +- Touch targets ≥24×24 (AA) or ≥44×44 (AAA). +- Spacing between targets to prevent mis-taps. +- Don't require complex gestures without simple alternatives (swipe → button). +- Support assistive touch and switch control. +- Ensure pinch-zoom works. +- Respect system text size settings (use `rem`/`em`). diff --git a/.agents/skills/project-discipline-guides/mirrors/accessibility/codex_mirror.md b/.agents/skills/project-discipline-guides/mirrors/accessibility/codex_mirror.md new file mode 100644 index 000000000..d7a09ee51 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/accessibility/codex_mirror.md @@ -0,0 +1,4900 @@ +# Accessibility Development Redundancy Mirror + +This is the accessibility guidance I would treat as broadly “common knowledge” and apply by default when building modern web interfaces, without needing a project-specific guide. + +## Core Mental Model + +Accessibility is not a separate feature. It is part of correctness, usability, semantic structure, interaction design, content design, and quality assurance. + +A robust accessible implementation usually depends on: + +- Semantic HTML first +- Native controls before custom widgets +- Keyboard support equivalent to pointer support +- Clear focus behavior +- Correct names, roles, states, and values +- Sufficient color contrast +- Usable text sizing and spacing +- Predictable navigation +- Error prevention and recovery +- Screen reader compatibility +- Support for reduced motion and other user preferences +- Progressive enhancement +- Testing with automated tools and manual assistive technology checks + +Accessibility should be considered during design and implementation, not patched afterward. + +## Standards And References + +The baseline standards I would assume: + +- WCAG 2.2 as the primary success criteria reference +- WCAG Level AA as the default target for most production sites +- WAI-ARIA Authoring Practices for custom widgets +- HTML accessibility mappings as the basis for native semantics +- Accessible Name and Description Computation for naming behavior +- Platform accessibility APIs as the reason semantic HTML matters +- Section 508 / EN 301 549 where legal compliance matters +- User agent and assistive technology behavior can vary, so test real combinations + +WCAG is not an implementation manual. Passing automated checks does not prove accessibility. Failing automated checks is usually actionable. + +## Prefer Semantic HTML + +Use semantic HTML whenever possible. + +Prefer: + +```html +<header> +<nav> +<main> +<section> +<article> +<aside> +<footer> +<button> +<a> +<label> +input +select +textarea +fieldset +legend +table +caption +th +``` + +Avoid unnecessary `div` and `span` elements when a semantic element exists. + +Semantic HTML gives browsers and assistive technologies built-in information about: + +- Role +- Name +- State +- Value +- Keyboard behavior +- Focus behavior +- Form behavior +- Landmark navigation +- Heading navigation +- Table navigation + +Do not recreate native HTML behavior with ARIA and JavaScript unless necessary. + +## ARIA First Rule + +Use native HTML first. + +ARIA should be used when: + +- Native semantics are insufficient +- A custom widget is genuinely necessary +- You need to expose dynamic state +- You need to connect labels, descriptions, errors, or relationships +- You need live-region announcements +- You need to clarify landmarks or complex regions + +Do not use ARIA to change semantics unnecessarily. + +Bad: + +```html +<div role="button" onclick="submitForm()">Submit</div> +``` + +Good: + +```html +<button type="submit">Submit</button> +``` + +Bad: + +```html +<button role="heading" aria-level="2">Settings</button> +``` + +Good: + +```html +<h2>Settings</h2> +<button>Open settings</button> +``` + +The five common ARIA rules: + +1. Use native HTML instead of ARIA when possible. +2. Do not change native semantics unless necessary. +3. Interactive ARIA widgets must be keyboard accessible. +4. Do not hide focusable elements from assistive technologies. +5. Interactive elements need accessible names. + +## Accessible Names + +Every interactive element needs an accessible name. + +Accessible names can come from: + +- Visible text +- `<label>` +- `aria-label` +- `aria-labelledby` +- `alt` +- `title` in limited fallback cases +- Associated table headers +- Button text +- Link text +- SVG title if properly referenced, though visible text is usually better + +Prefer visible text or `aria-labelledby` over `aria-label` when possible. + +Good: + +```html +<button>Save changes</button> +``` + +Good icon button: + +```html +<button type="button" aria-label="Search"> + <svg aria-hidden="true" focusable="false">...</svg> +</button> +``` + +Good label association: + +```html +<label for="email">Email address</label> +<input id="email" name="email" type="email" autocomplete="email"> +``` + +Good `aria-labelledby`: + +```html +<h2 id="billing-heading">Billing address</h2> +<section aria-labelledby="billing-heading"> + ... +</section> +``` + +Avoid redundant names: + +```html +<button aria-label="Save">Save</button> +``` + +This is usually unnecessary and can become stale. + +## Accessible Descriptions + +Use descriptions for supporting information, not names. + +```html +<label for="password">Password</label> +<p id="password-help">Use at least 12 characters.</p> +<input id="password" type="password" aria-describedby="password-help"> +``` + +Use `aria-describedby` for: + +- Help text +- Error messages +- Formatting hints +- Constraints +- Supplemental context + +Use `aria-errormessage` when marking invalid controls, but ensure support and behavior are tested. + +```html +<label for="zip">ZIP code</label> +<input + id="zip" + name="zip" + aria-invalid="true" + aria-errormessage="zip-error" +> +<p id="zip-error">Enter a valid 5-digit ZIP code.</p> +``` + +## Buttons And Links + +Use links for navigation. + +```html +<a href="/account">Account</a> +``` + +Use buttons for actions. + +```html +<button type="button">Open menu</button> +``` + +Do not use links as buttons unless navigation actually occurs. + +Avoid: + +```html +<a href="#" onclick="openModal()">Open modal</a> +``` + +Prefer: + +```html +<button type="button">Open modal</button> +``` + +Links need meaningful `href` values. Placeholder links are not accessible. + +Avoid vague link text: + +```html +<a href="/report">Click here</a> +``` + +Prefer: + +```html +<a href="/report">Read the annual report</a> +``` + +Repeated links can share visible text if context makes them distinguishable, but unique text is often better. + +## Keyboard Accessibility + +All interactive functionality must be operable by keyboard. + +Users must be able to: + +- Reach controls with `Tab` +- Move backward with `Shift+Tab` +- Activate buttons with `Enter` and `Space` +- Activate links with `Enter` +- Use arrow keys where expected for composite widgets +- Escape dismissible overlays with `Escape` +- Understand current focus +- Avoid keyboard traps + +Do not remove focusability from interactive controls. + +Do not rely on mouse-only events: + +```js +element.addEventListener("mouseover", showTooltip); +``` + +Also support focus: + +```js +element.addEventListener("focus", showTooltip); +element.addEventListener("blur", hideTooltip); +``` + +Pointer interactions should usually have keyboard equivalents. + +Avoid positive `tabindex`. + +Bad: + +```html +<div tabindex="5">...</div> +``` + +Use: + +```html +<button>...</button> +``` + +or, only when necessary: + +```html +<div tabindex="0">...</div> +``` + +Use `tabindex="-1"` for programmatic focus targets that should not be in the normal tab order. + +```html +<h1 tabindex="-1">Checkout</h1> +``` + +## Focus Management + +Focus must move predictably. + +Common focus rules: + +- Do not remove the visible focus indicator. +- After opening a modal dialog, move focus into it. +- Trap focus inside modal dialogs while open. +- Return focus to the triggering control when the dialog closes. +- After client-side navigation, move focus to the main heading or main container. +- After validation failure, focus the first invalid field or an error summary. +- Do not unexpectedly move focus during ordinary typing or reading. +- Do not focus disabled or hidden elements. +- Do not leave focus behind inside removed DOM nodes. +- Use `preventScroll` when moving focus would otherwise create disorienting jumps. + +```js +heading.focus({ preventScroll: true }); +``` + +Use `:focus-visible` for focus styling. + +```css +:focus-visible { + outline: 2px solid CanvasText; + outline-offset: 2px; +} +``` + +Avoid: + +```css +*:focus { + outline: none; +} +``` + +If replacing outlines, provide a visible alternative with sufficient contrast. + +## Focus Styling + +Focus indicators should be: + +- Clearly visible +- Not dependent on color alone +- At least as visible as the browser default +- Not clipped by overflow +- Present in high contrast / forced color modes +- Consistent across components + +Good: + +```css +.button:focus-visible { + outline: 3px solid currentColor; + outline-offset: 3px; +} +``` + +For complex components, style the actual focused item, not only a parent. + +In Windows forced colors mode, prefer system colors and avoid relying only on box shadows. + +```css +@media (forced-colors: active) { + .button:focus-visible { + outline: 2px solid Highlight; + } +} +``` + +## Landmarks + +Use landmarks to help users navigate. + +Common landmarks: + +```html +<header> +<nav aria-label="Primary"> +<main> +<aside> +<footer> +<form> +<section aria-labelledby="..."> +``` + +Every page should usually have one `<main>`. + +Use labels when multiple landmarks of the same type exist. + +```html +<nav aria-label="Primary"> +<nav aria-label="Breadcrumb"> +<nav aria-label="Pagination"> +``` + +Avoid excessive landmarks. Too many landmarks reduce usefulness. + +## Headings + +Use headings to describe document structure. + +```html +<h1>Account settings</h1> +<h2>Profile</h2> +<h2>Security</h2> +<h3>Password</h3> +``` + +Guidance: + +- Use one primary `<h1>` for the page or view. +- Do not skip heading levels for styling reasons. +- Use CSS for visual size, not heading rank. +- Do not use headings only to make text large. +- Headings should describe the content that follows. +- Repeated components can use headings if they create meaningful structure. + +Heading levels should reflect information architecture, not component nesting alone. + +## Page Titles + +Each page should have a meaningful `<title>`. + +```html +<title>Billing settings | Acme +``` + +For single-page apps, update `document.title` on route changes. + +Titles should identify the current page or state, especially after navigation or errors. + +## Language + +Set the document language. + +```html + +``` + +Mark language changes inline when meaningful. + +```html +

The phrase bon voyage means have a good trip.

+``` + +This helps pronunciation, spellchecking, translation, and assistive technology. + +## Text Alternatives + +Images need appropriate alternatives. + +Informative image: + +```html +Revenue increased from $4M in 2023 to $6M in 2024. +``` + +Decorative image: + +```html + +``` + +Functional image: + +```html + +``` + +If an image is inside a link or button and is the only content, its `alt` should describe the action or destination. + +```html + + Acme home + +``` + +Avoid: + +- `alt="image"` +- `alt="photo"` +- `alt="icon"` +- File names as alt text +- Duplicating nearby text unnecessarily +- Overly verbose alt text when surrounding text already explains the image + +Complex images, charts, diagrams, and maps need nearby explanations, data tables, summaries, or long descriptions. + +## SVG Accessibility + +Decorative SVG: + +```html + +``` + +Meaningful inline SVG: + +```html + + Sales by region + ... + +``` + +Icon inside named button: + +```html + +``` + +Do not rely on SVG `` alone for consistently accessible button names unless tested. Prefer naming the button. + +## Icon Buttons + +Icon-only buttons need accessible names. + +```html +<button type="button" aria-label="Close"> + <svg aria-hidden="true" focusable="false">...</svg> +</button> +``` + +The accessible name should describe the action, not the icon. + +Good: + +```html +aria-label="Delete" +``` + +Bad: + +```html +aria-label="Trash can" +``` + +If button state changes, include state separately when possible: + +```html +<button type="button" aria-pressed="false"> + Bold +</button> +``` + +Avoid changing the accessible name based only on state unless the UX clearly requires it. + +## Forms + +Use real form controls. + +```html +<form> + <label for="name">Name</label> + <input id="name" name="name" autocomplete="name"> + + <button type="submit">Save</button> +</form> +``` + +Every input needs a label. + +Do not use placeholders as labels. + +Bad: + +```html +<input placeholder="Email"> +``` + +Good: + +```html +<label for="email">Email</label> +<input id="email" name="email" type="email" autocomplete="email"> +``` + +Use correct input types: + +```html +<input type="email"> +<input type="tel"> +<input type="url"> +<input type="number"> +<input type="search"> +<input type="date"> +<input type="time"> +<input type="password"> +``` + +Use `autocomplete` tokens where appropriate: + +```html +<input autocomplete="name"> +<input autocomplete="email"> +<input autocomplete="username"> +<input autocomplete="current-password"> +<input autocomplete="new-password"> +<input autocomplete="street-address"> +<input autocomplete="postal-code"> +<input autocomplete="cc-number"> +``` + +Group related controls with `fieldset` and `legend`. + +```html +<fieldset> + <legend>Notification preferences</legend> + + <label> + <input type="checkbox" name="notify" value="email"> + Email + </label> + + <label> + <input type="checkbox" name="notify" value="sms"> + SMS + </label> +</fieldset> +``` + +For required fields, use `required`. + +```html +<input id="email" required> +``` + +If indicating required visually, expose it programmatically or include it in text. + +```html +<label for="email">Email <span aria-hidden="true">*</span></label> +``` + +Better: + +```html +<label for="email">Email required</label> +``` + +## Form Validation + +Validation should be: + +- Programmatically exposed +- Visible +- Specific +- Timely but not disruptive +- Associated with the relevant control +- Summarized for larger forms +- Not dependent on color alone + +Example: + +```html +<label for="email">Email</label> +<input + id="email" + name="email" + type="email" + required + aria-invalid="true" + aria-describedby="email-error" +> +<p id="email-error">Enter an email address in the format name@example.com.</p> +``` + +After submit failure, an error summary can help. + +```html +<div role="alert" tabindex="-1" id="error-summary"> + <h2>There is a problem</h2> + <ul> + <li><a href="#email">Enter a valid email address.</a></li> + </ul> +</div> +``` + +Then move focus to the summary or first invalid field depending on context. + +Do not clear user-entered values after errors. + +Do not validate only on blur if it creates noisy interruptions. Avoid announcing errors while the user is still typing unless necessary. + +## Required, Disabled, And Readonly + +Use native attributes. + +```html +<input required> +<input disabled> +<input readonly> +``` + +`disabled` controls: + +- Are not focusable +- Are not submitted +- Are announced as disabled +- May be skipped by assistive tech navigation + +Use `aria-disabled="true"` only when an element must remain focusable while unavailable, and prevent activation manually. + +```html +<button aria-disabled="true">Continue</button> +``` + +With `aria-disabled`, JavaScript must block activation. + +Readonly controls can still be focusable and submitted. + +## Placeholder Text + +Placeholders are not labels. + +Problems with placeholders: + +- They disappear during input +- They often have low contrast +- They are not consistently announced as labels +- They make review harder +- They can be mistaken for entered values + +Use placeholders only for supplemental examples, not essential instructions. + +```html +<label for="phone">Phone number</label> +<input id="phone" autocomplete="tel" placeholder="555-123-4567"> +``` + +## Custom Controls + +Prefer native controls. If custom controls are necessary, implement: + +- Correct role +- Accessible name +- Keyboard interaction +- Focus management +- State attributes +- Value attributes +- Disabled behavior +- High contrast styling +- Pointer, touch, keyboard, and screen reader behavior +- Form integration if relevant + +A custom checkbox needs: + +```html +<div + role="checkbox" + tabindex="0" + aria-checked="false" +> + Subscribe +</div> +``` + +But native is better: + +```html +<label> + <input type="checkbox"> + Subscribe +</label> +``` + +Custom widgets should follow expected keyboard conventions from ARIA Authoring Practices. + +## `role` Usage + +Common roles: + +```html +role="button" +role="checkbox" +role="radio" +role="switch" +role="tablist" +role="tab" +role="tabpanel" +role="dialog" +role="alertdialog" +role="menu" +role="menuitem" +role="listbox" +role="option" +role="combobox" +role="grid" +role="row" +role="cell" +role="status" +role="alert" +role="region" +role="img" +``` + +Use roles only when the semantic meaning is correct. + +Do not use `role="menu"` for ordinary site navigation. ARIA menus are application-style widgets with specific keyboard behavior. Use `<nav>` and links for navigation. + +Do not use `role="application"` unless there is a strong reason. It changes screen reader interaction expectations and is often harmful. + +Use `role="presentation"` or `role="none"` only to remove semantics from non-interactive elements. + +Never hide or neutralize semantics of focusable/interacting elements. + +## ARIA States And Properties + +Common ARIA attributes: + +```html +aria-label +aria-labelledby +aria-describedby +aria-controls +aria-expanded +aria-current +aria-selected +aria-checked +aria-pressed +aria-disabled +aria-hidden +aria-invalid +aria-required +aria-live +aria-atomic +aria-busy +aria-modal +aria-haspopup +aria-activedescendant +aria-owns +``` + +Use ARIA state accurately. + +Disclosure button: + +```html +<button + type="button" + aria-expanded="false" + aria-controls="filters" +> + Filters +</button> + +<div id="filters" hidden> + ... +</div> +``` + +Current page: + +```html +<a href="/pricing" aria-current="page">Pricing</a> +``` + +Pressed toggle: + +```html +<button type="button" aria-pressed="true">Bold</button> +``` + +Selected tab: + +```html +<button role="tab" aria-selected="true" aria-controls="panel-1"> + Details +</button> +``` + +Do not use ARIA attributes as styling-only state if the exposed state is not true. + +## `aria-hidden` + +`aria-hidden="true"` hides content from assistive technologies. + +Use it for: + +- Decorative icons +- Duplicated visual text +- Offscreen visual-only decorations +- Background content made inert by a modal, if not using `inert` + +Do not put `aria-hidden="true"` on focusable elements or ancestors of focusable elements. + +Bad: + +```html +<div aria-hidden="true"> + <button>Still focusable</button> +</div> +``` + +Use `hidden`, `display: none`, `inert`, or remove from DOM when content should be unavailable. + +## `hidden`, CSS Hiding, And Visually Hidden Text + +`hidden` removes content from visual display and accessibility tree. + +```html +<div hidden>...</div> +``` + +`display: none` and `visibility: hidden` also hide from assistive technologies. + +Visually hidden text should remain available to screen readers. + +```css +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + border: 0; +} +``` + +Use visually hidden text for: + +- Extra context for icon buttons +- Table captions when visual design omits them +- Skip link text +- Clarifying repeated controls + +Do not use visually hidden content to dump excessive instructions for screen reader users only. Equivalent information should generally be available to everyone. + +## `inert` + +Use `inert` to make background content unavailable while a modal or blocking overlay is active. + +```html +<main inert> + ... +</main> +``` + +`inert` removes descendants from sequential focus navigation and the accessibility tree, and blocks interaction. + +Use it for: + +- Modal background content +- Temporarily disabled page regions +- Hidden offcanvas UI that remains in the DOM + +Still manage focus explicitly for modals and restore focus when closing. + +If supporting older browsers, use a polyfill or fallback with focus trapping and `aria-hidden`. + +## Dialogs And Modals + +Prefer the native `<dialog>` element when appropriate and tested. + +```html +<dialog id="settings-dialog" aria-labelledby="settings-title"> + <h2 id="settings-title">Settings</h2> + <form method="dialog"> + <button>Close</button> + </form> +</dialog> +``` + +Open modal dialogs with: + +```js +dialog.showModal(); +``` + +Close with: + +```js +dialog.close(); +``` + +Modal requirements: + +- Accessible name via heading, `aria-label`, or `aria-labelledby` +- Focus moves into the dialog when opened +- Focus stays inside while modal +- `Escape` closes unless there is a good reason +- Focus returns to the invoker after close +- Background is inert/unavailable +- Dialog is not announced without context +- Close button is available and named +- Large dialogs remain scrollable and usable +- Initial focus is chosen intentionally + +Use `role="dialog"` and `aria-modal="true"` for custom dialogs. + +```html +<div + role="dialog" + aria-modal="true" + aria-labelledby="dialog-title" +> + <h2 id="dialog-title">Delete item</h2> + ... +</div> +``` + +Use `role="alertdialog"` for urgent confirmation or destructive decisions requiring immediate attention. + +## Popovers + +Use the Popover API as progressive enhancement for non-modal overlays where supported. + +```html +<button popovertarget="menu">Options</button> + +<div id="menu" popover> + ... +</div> +``` + +Popover is useful for: + +- Lightweight menus +- Teaching UI +- Non-modal flyouts +- Small overlays +- Dismissible panels + +A popover is not automatically a full accessible menu, dialog, tooltip, or combobox. You still need the correct semantics and keyboard behavior for the pattern. + +For modal workflows, use `<dialog>` rather than popover. + +For custom widgets, ensure focus, dismissal, and state are handled. + +## Tooltips + +Tooltips should not contain essential information. + +Accessible tooltip guidance: + +- Trigger on hover and focus +- Dismiss on blur, mouseout, and `Escape` +- Do not require pointer precision +- Do not contain interactive content +- Keep content short +- Associate with the trigger using `aria-describedby` when appropriate +- Ensure tooltip content is available to keyboard and assistive technology users + +If the content is essential, place it visibly in the UI instead of hiding it in a tooltip. + +## Menus + +Use ARIA menus only for application-style command menus, not ordinary navigation. + +For site navigation: + +```html +<nav aria-label="Primary"> + <ul> + <li><a href="/products">Products</a></li> + <li><a href="/pricing">Pricing</a></li> + </ul> +</nav> +``` + +For real menu buttons: + +```html +<button + type="button" + aria-haspopup="menu" + aria-expanded="false" + aria-controls="actions-menu" +> + Actions +</button> + +<ul id="actions-menu" role="menu" hidden> + <li role="none"><button role="menuitem">Rename</button></li> + <li role="none"><button role="menuitem">Delete</button></li> +</ul> +``` + +Expected menu keyboard behavior usually includes: + +- `Enter` / `Space` opens or activates +- Arrow keys move through items +- `Home` / `End` move to first/last +- `Escape` closes and returns focus +- Typeahead may be supported + +Do not use `role="menuitem"` for regular navigation links unless implementing full menu behavior. + +## Tabs + +Tabs need coordinated roles, states, and focus behavior. + +```html +<div role="tablist" aria-label="Account sections"> + <button + role="tab" + id="tab-profile" + aria-selected="true" + aria-controls="panel-profile" + > + Profile + </button> + <button + role="tab" + id="tab-security" + aria-selected="false" + aria-controls="panel-security" + tabindex="-1" + > + Security + </button> +</div> + +<section + role="tabpanel" + id="panel-profile" + aria-labelledby="tab-profile" +> + ... +</section> + +<section + role="tabpanel" + id="panel-security" + aria-labelledby="tab-security" + hidden +> + ... +</section> +``` + +Expected behavior: + +- Only active tab is usually in the tab order +- Arrow keys move focus between tabs +- `Home` and `End` move to first/last tab +- `Enter` or `Space` activates if activation is manual +- Active tab has `aria-selected="true"` +- Inactive panels are hidden + +Automatic activation is acceptable when panels load instantly. Manual activation is better if loading is slow. + +## Accordions And Disclosures + +Use buttons for expandable section headers. + +```html +<h2> + <button + type="button" + aria-expanded="false" + aria-controls="section-1" + > + Shipping address + </button> +</h2> + +<div id="section-1" hidden> + ... +</div> +``` + +Use `aria-expanded` on the button that controls the content. + +Keep heading structure meaningful. + +Do not make non-button headings clickable without keyboard support. + +## Carousels + +Carousels are often accessibility risks. + +If used: + +- Provide pause/stop controls +- Do not auto-advance indefinitely +- Pause on focus and hover +- Do not move focus unexpectedly +- Announce slide changes only when user initiated +- Use real buttons for previous/next +- Expose current slide and total count +- Ensure all slide content is reachable +- Hide inactive slides appropriately if they are not meant to be read +- Make controls large enough for touch +- Avoid motion that conflicts with reduced-motion preferences + +Prefer static content when carousel behavior is not essential. + +## Tables + +Use tables for tabular data, not layout. + +Simple table: + +```html +<table> + <caption>Monthly expenses</caption> + <thead> + <tr> + <th scope="col">Month</th> + <th scope="col">Rent</th> + <th scope="col">Utilities</th> + </tr> + </thead> + <tbody> + <tr> + <th scope="row">January</th> + <td>$1,500</td> + <td>$180</td> + </tr> + </tbody> +</table> +``` + +Use: + +- `<caption>` for table title/summary +- `<th>` for headers +- `scope="col"` and `scope="row"` for simple tables +- `headers` / `id` for complex tables when necessary + +Avoid: + +- Empty header cells +- Layout tables +- Div-based data tables without semantics +- Breaking table display semantics with CSS in ways that harm accessibility +- Overly complex responsive transformations that destroy relationships + +For responsive tables, preserve header/data relationships. + +## Lists + +Use lists for list-like content. + +```html +<ul> + <li>Apples</li> + <li>Oranges</li> +</ul> +``` + +Use ordered lists when sequence matters. + +```html +<ol> + <li>Create account</li> + <li>Verify email</li> + <li>Set password</li> +</ol> +``` + +Do not remove list semantics when the content is still a list. + +Be careful with CSS resets that strip list markers and semantics. + +## Navigation + +Navigation should be consistent and predictable. + +Use: + +```html +<nav aria-label="Primary"> +``` + +Current page: + +```html +<a href="/settings" aria-current="page">Settings</a> +``` + +Breadcrumbs: + +```html +<nav aria-label="Breadcrumb"> + <ol> + <li><a href="/">Home</a></li> + <li><a href="/account">Account</a></li> + <li aria-current="page">Settings</li> + </ol> +</nav> +``` + +Pagination: + +```html +<nav aria-label="Pagination"> + <a href="?page=1">Previous</a> + <a href="?page=3" aria-current="page">Page 3</a> + <a href="?page=4">Next</a> +</nav> +``` + +Avoid navigation that changes on focus or hover unexpectedly. + +## Skip Links + +Provide a skip link for pages with repeated navigation. + +```html +<a class="skip-link" href="#main">Skip to main content</a> + +<main id="main"> + ... +</main> +``` + +CSS: + +```css +.skip-link { + position: absolute; + left: 1rem; + top: 1rem; + transform: translateY(-200%); +} + +.skip-link:focus { + transform: translateY(0); +} +``` + +Ensure the target can receive focus if needed. + +```html +<main id="main" tabindex="-1"> +``` + +## Single Page Apps + +For client-side routing: + +- Update document title +- Move focus to the new page heading or main region +- Announce route changes when appropriate +- Preserve expected browser history +- Ensure back/forward works +- Avoid scroll jumps unless intentional +- Restore scroll thoughtfully +- Do not leave focus on removed controls +- Ensure landmarks and headings represent the current view + +Example route focus target: + +```html +<main id="main" tabindex="-1"> + <h1>Orders</h1> +</main> +``` + +```js +document.title = "Orders | Acme"; +document.querySelector("#main")?.focus(); +``` + +## Live Regions + +Use live regions for dynamic updates that are not focused. + +Polite status: + +```html +<div role="status" aria-live="polite"></div> +``` + +Assertive alert: + +```html +<div role="alert"></div> +``` + +Guidance: + +- Use `polite` for non-urgent updates. +- Use `assertive` only for urgent interruptions. +- Keep announcements short. +- Do not announce every keystroke unless necessary. +- Insert or update text after the live region exists in the DOM. +- Avoid duplicate announcements. +- Use `aria-atomic="true"` when the whole message should be read. +- Use `aria-busy="true"` while a region is loading. + +Example: + +```html +<p id="save-status" role="status" aria-live="polite"></p> +``` + +```js +status.textContent = "Changes saved."; +``` + +Use live regions for: + +- Save success +- Async errors +- Search result counts +- Cart updates +- Background process completion +- Form submission status + +Do not use live regions as a replacement for visible feedback. + +## Loading States + +Loading states should be communicated visually and programmatically. + +```html +<section aria-busy="true" aria-labelledby="results-title"> + <h2 id="results-title">Search results</h2> + ... +</section> +``` + +For buttons: + +```html +<button type="submit" disabled> + Saving... +</button> +``` + +Consider whether disabled is appropriate. If disabling prevents double submit, it is often fine. If the user needs to understand why an action is unavailable, provide explanation. + +For spinners, include text. + +```html +<div role="status"> + <span class="spinner" aria-hidden="true"></span> + Loading orders... +</div> +``` + +Avoid spinner-only loading indicators. + +## Motion And Animation + +Respect reduced motion. + +```css +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms; + animation-iteration-count: 1; + scroll-behavior: auto; + transition-duration: 0.01ms; + } +} +``` + +Better when possible: selectively remove non-essential motion rather than globally flattening everything. + +Avoid: + +- Parallax that cannot be disabled +- Auto-playing motion +- Flashing content +- Large unexpected zoom/pan +- Scroll-jacking +- Motion required to understand content + +Use `prefers-reduced-motion` in JavaScript: + +```js +const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; +``` + +Do not animate focus movement in a way that delays usability. + +## Flashing And Seizure Safety + +Avoid content that flashes more than three times per second. + +Avoid high-contrast flashing, especially red flashes. + +Provide controls for animated content. + +Do not use strobe effects. + +## Color And Contrast + +Text contrast default target: + +- 4.5:1 for normal text +- 3:1 for large text +- 3:1 for graphical objects and UI components +- Disabled controls are exempt but should still be understandable where possible + +Do not use color alone to convey meaning. + +Bad: + +```html +<p class="red">Required</p> +``` + +Good: + +```html +<p> + <span aria-hidden="true">*</span> + Required fields are marked with an asterisk. +</p> +``` + +For charts, use: + +- Labels +- Patterns +- Shapes +- Direct annotations +- Legends with text +- Tooltips that are keyboard accessible if essential + +Ensure contrast in: + +- Text +- Placeholder text +- Borders that indicate state +- Focus indicators +- Icons +- Disabled controls where useful +- Hover states +- Selection states +- Error states +- High contrast mode + +## Forced Colors And High Contrast + +Support forced colors. + +```css +@media (forced-colors: active) { + .button { + border: 1px solid ButtonText; + } + + .button:focus-visible { + outline: 2px solid Highlight; + } +} +``` + +Avoid relying only on: + +- Box shadow +- Background gradients +- Low-contrast borders +- Background images +- Color-only state + +Use system colors when appropriate: + +```css +color: CanvasText; +background: Canvas; +border-color: ButtonText; +``` + +Avoid disabling forced color adjustments unless absolutely necessary. + +```css +forced-color-adjust: none; +``` + +Use it sparingly. + +## User Preferences Media Queries + +Modern accessibility-related media queries include: + +```css +@media (prefers-reduced-motion: reduce) {} +@media (prefers-reduced-transparency: reduce) {} +@media (prefers-contrast: more) {} +@media (prefers-color-scheme: dark) {} +@media (forced-colors: active) {} +``` + +Use them as progressive enhancement. + +`prefers-reduced-transparency` and `prefers-contrast` may not be uniformly supported everywhere, so design should remain usable without them. + +## Text Sizing And Zoom + +Interfaces should work at: + +- 200% browser zoom +- Large default font sizes +- Mobile text scaling +- Narrow viewports +- Reflow without horizontal scrolling for normal content + +Use relative units for text: + +```css +font-size: 1rem; +line-height: 1.5; +``` + +Avoid fixed pixel heights for text containers. + +Avoid clipping text: + +```css +overflow: hidden; +white-space: nowrap; +``` + +unless truncation is intentional and full content is available. + +Support WCAG text spacing expectations: + +- Increased line height +- Paragraph spacing +- Letter spacing +- Word spacing + +Do not break when users override spacing. + +Avoid disabling zoom. + +Bad: + +```html +<meta name="viewport" content="user-scalable=no"> +``` + +Good: + +```html +<meta name="viewport" content="width=device-width, initial-scale=1"> +``` + +## Responsive Accessibility + +Responsive design must preserve accessibility. + +Check: + +- Content order matches visual order +- Keyboard order is logical +- Focus is visible +- Controls remain large enough +- No content is clipped +- No horizontal scrolling for ordinary text +- Landmarks and headings remain meaningful +- Menus are keyboard accessible +- Touch targets are large enough +- Modals fit small screens +- Sticky headers do not cover focused content + +Avoid CSS `order` or grid placement that creates a mismatch between visual order and DOM/focus order. + +## Touch And Pointer Accessibility + +Touch targets should be large enough and spaced well. + +Common target guidance: + +- At least 24 CSS px for WCAG 2.2 minimum target size in many contexts +- 44 by 44 CSS px remains a strong practical target +- Provide spacing between adjacent controls +- Avoid tiny close buttons +- Do not require drag gestures without alternatives +- Do not require multi-pointer gestures without alternatives +- Support keyboard and single-pointer alternatives + +Pointer cancellation: + +- Avoid triggering destructive actions on pointer down +- Prefer activation on pointer up/click +- Allow users to cancel by moving away before release +- Confirm destructive actions where appropriate + +## Drag And Drop + +Drag-and-drop must have accessible alternatives. + +Provide: + +- Keyboard controls +- Buttons for move up/down +- Select menus for destination +- Clear instructions +- Status announcements +- Focus preservation +- Undo where possible + +Example alternatives: + +```html +<button type="button">Move item up</button> +<button type="button">Move item down</button> +``` + +Do not make drag the only way to reorder or move content. + +## Gestures + +Do not require complex gestures as the only way to complete a task. + +If supporting: + +- Swipe +- Pinch +- Drag +- Long press +- Multi-touch + +Provide alternatives using buttons or other simple controls. + +## Target Size + +Interactive targets should be easy to activate. + +Consider: + +- Actual clickable area, not just visual icon +- Spacing between controls +- Touch input +- Motor impairments +- Zoomed layouts +- Dense toolbars +- Mobile devices + +Icon buttons should have adequate padding. + +```css +.icon-button { + inline-size: 2.75rem; + block-size: 2.75rem; +} +``` + +## Content Order + +DOM order should match reading and focus order. + +Avoid using CSS to visually rearrange content in ways that make screen reader or keyboard order confusing. + +Bad pattern: + +```css +.sidebar { + order: -1; +} +``` + +This may visually move content before main content while keyboard order remains later. + +Use source order as the accessibility order. + +## Reading Level And Content Clarity + +Accessible content is clear content. + +Use: + +- Plain language +- Specific headings +- Descriptive button text +- Descriptive link text +- Short paragraphs +- Lists where appropriate +- Clear error messages +- Consistent terminology +- Defined abbreviations +- Helpful page titles + +Avoid: + +- Jargon without explanation +- Instructions based only on sensory characteristics +- “Click the green button” +- “Use the menu on the right” +- Ambiguous labels like “Submit” when more specific text would help + +Better: + +```html +<button>Save billing address</button> +``` + +instead of: + +```html +<button>Submit</button> +``` + +## Sensory Characteristics + +Do not rely only on: + +- Color +- Shape +- Size +- Position +- Sound +- Motion + +Bad: + +```text +Press the green button to continue. +``` + +Good: + +```text +Press Continue to continue. +``` + +## Audio And Video + +Media accessibility includes: + +- Captions for prerecorded video with audio +- Captions for live video when required +- Transcripts for audio +- Audio descriptions or equivalent alternatives for important visual-only content +- Keyboard-accessible media controls +- No autoplaying audio +- Pause/stop controls +- Visible focus indicators on controls +- Sufficient contrast in custom controls + +Use native media controls when possible. + +```html +<video controls> + <source src="demo.mp4" type="video/mp4"> + <track kind="captions" src="captions.vtt" srclang="en" label="English"> +</video> +``` + +Do not autoplay audio without user initiation. + +If video autoplays silently, provide controls and respect reduced motion. + +## Autoplaying Content + +Avoid autoplay. + +If content moves, blinks, scrolls, or updates automatically for more than a short duration, provide controls to pause, stop, or hide it. + +Examples: + +- Carousels +- Tickers +- Animated ads +- Auto-updating feeds +- Background videos + +Do not reset pause state unexpectedly. + +## Time Limits + +Avoid time limits when possible. + +If a time limit exists: + +- Warn users +- Allow extension +- Allow disabling when feasible +- Preserve work +- Provide accessible countdowns only when useful +- Avoid overly frequent live announcements + +For security sessions, warn before expiration and provide a way to extend. + +## Authentication + +Authentication should not rely only on cognitive function tests. + +Avoid making users solve inaccessible puzzles. + +CAPTCHA alternatives: + +- Passkeys +- Email verification +- SMS with accessible fallback +- Server-side risk detection +- Accessible CAPTCHA alternatives +- Human support path for critical services + +One-time code inputs should support autocomplete: + +```html +<input + inputmode="numeric" + autocomplete="one-time-code" +> +``` + +Password fields should support password managers: + +```html +<input + type="password" + autocomplete="current-password" +> +``` + +Do not block paste into password or code fields. + +## Cognitive Accessibility + +Support users with cognitive, memory, language, and attention differences. + +Good practices: + +- Keep workflows predictable +- Use consistent navigation +- Make important actions clear +- Avoid unnecessary time pressure +- Provide confirmation for destructive actions +- Provide undo where possible +- Break complex tasks into steps +- Show progress in multi-step flows +- Preserve entered data +- Avoid surprise context changes +- Use clear labels and instructions +- Provide examples for complex formats +- Avoid distracting motion +- Avoid dense unexplained interfaces + +## Errors And Recovery + +Good error messages: + +- Identify the field +- Explain the problem +- Explain how to fix it +- Are programmatically associated +- Are visible +- Are not color-only +- Preserve user input + +Bad: + +```text +Invalid input. +``` + +Good: + +```text +Enter an expiration date in MM/YY format. +``` + +For destructive actions: + +- Use clear names +- Confirm where appropriate +- Explain consequences +- Provide undo if possible + +## Status Messages + +Status messages should be visible and announced when relevant. + +Examples: + +```html +<div role="status">File uploaded.</div> +``` + +Use for non-focus-changing updates. + +Do not steal focus for routine status updates. + +Use alerts sparingly: + +```html +<div role="alert">Payment failed. Check your card details.</div> +``` + +## Search + +Search forms should be semantic. + +```html +<form role="search"> + <label for="site-search">Search</label> + <input id="site-search" name="q" type="search"> + <button type="submit">Search</button> +</form> +``` + +For dynamic results: + +- Announce result counts +- Keep keyboard focus predictable +- Do not update results so aggressively that screen readers are interrupted +- Provide loading status +- Preserve query text + +## Comboboxes And Autocomplete + +Comboboxes are complex. Prefer native controls when possible. + +Native alternatives: + +```html +<select> +``` + +```html +<input list="cities"> +<datalist id="cities"> + <option value="New York"> + <option value="Los Angeles"> +</datalist> +``` + +If implementing a custom combobox, handle: + +- `role="combobox"` +- Accessible name +- Expanded/collapsed state +- Popup relationship +- Options +- Active descendant or roving focus +- Keyboard navigation +- Escape behavior +- Enter selection +- Typing behavior +- Screen reader announcements +- Mobile behavior +- Form submission +- Loading state +- No results state + +ARIA combobox behavior is easy to get wrong. Use a well-tested component when possible. + +## Selects, Listboxes, And Radios + +Use native controls unless custom behavior is required. + +Radio group: + +```html +<fieldset> + <legend>Shipping speed</legend> + + <label> + <input type="radio" name="shipping" value="standard"> + Standard + </label> + + <label> + <input type="radio" name="shipping" value="express"> + Express + </label> +</fieldset> +``` + +Select: + +```html +<label for="country">Country</label> +<select id="country" name="country"> + <option value="">Select a country</option> + <option value="us">United States</option> +</select> +``` + +Do not replace select controls for styling alone unless the custom replacement is fully accessible. + +## Switches + +Use a checkbox for binary settings, or `role="switch"` when the on/off metaphor is important. + +Native checkbox: + +```html +<label> + <input type="checkbox" name="marketing"> + Receive marketing emails +</label> +``` + +Switch: + +```html +<button type="button" role="switch" aria-checked="false"> + Email notifications +</button> +``` + +The label should not change between on and off if state is exposed through `aria-checked`. + +## Toggle Buttons + +Use `aria-pressed` for toggle buttons. + +```html +<button type="button" aria-pressed="false"> + Bold +</button> +``` + +Do not use `aria-checked` on buttons unless the role supports it. + +Keep labels stable where possible. + +## Progress Indicators + +Use native progress when appropriate. + +```html +<label for="upload-progress">Upload progress</label> +<progress id="upload-progress" max="100" value="70">70%</progress> +``` + +For indeterminate progress: + +```html +<progress>Loading...</progress> +``` + +For custom progress bars: + +```html +<div + role="progressbar" + aria-valuemin="0" + aria-valuemax="100" + aria-valuenow="70" + aria-label="Upload progress" +></div> +``` + +## Sliders + +Prefer native range inputs. + +```html +<label for="volume">Volume</label> +<input id="volume" type="range" min="0" max="100" value="50"> +``` + +Custom sliders need: + +- `role="slider"` +- Name +- `aria-valuemin` +- `aria-valuemax` +- `aria-valuenow` +- `aria-valuetext` when numeric value is not meaningful +- Keyboard support for arrow keys, Home, End, Page Up, Page Down where appropriate +- Touch and pointer support + +## Disclosure Navigation + +For mobile nav menus: + +```html +<button + type="button" + aria-expanded="false" + aria-controls="primary-navigation" +> + Menu +</button> + +<nav id="primary-navigation" aria-label="Primary" hidden> + ... +</nav> +``` + +When opened: + +- Update `aria-expanded` +- Reveal menu +- Ensure focus order is logical +- Allow Escape to close +- Return focus when closing where appropriate + +Do not use hover-only navigation. + +## Hover And Focus Content + +Content that appears on hover or focus should be: + +- Dismissible +- Hoverable if pointer-triggered +- Persistent long enough to use +- Triggered by focus as well as hover +- Not obscure important content unless dismissible + +This applies to: + +- Tooltips +- Popovers +- Menus +- Preview cards +- Help bubbles + +## Modality-Aware Focus + +Use `:focus-visible` to show focus mainly for keyboard modality while preserving accessibility. + +```css +:focus-visible { + outline: 2px solid currentColor; +} +``` + +Do not hide focus for mouse users if doing so harms discoverability or forced-colors behavior. + +## CSS Generated Content + +Do not put essential text only in CSS generated content. + +Bad: + +```css +.required::after { + content: "required"; +} +``` + +Generated content may not be reliably exposed across assistive technologies. + +Essential content should exist in the DOM. + +## Visual Reordering + +Be careful with: + +```css +order +grid-area +flex-direction: row-reverse +``` + +These can create mismatches between visual order and DOM/focus order. + +Use DOM order as the meaningful order. + +## Offscreen Content + +Do not place focusable content offscreen unless it is intentionally reachable, such as a skip link. + +Bad: + +```css +.panel { + position: absolute; + left: -9999px; +} +``` + +If hidden, use `hidden`, `display: none`, or `inert` depending on intent. + +## Responsive Hidden Content + +When hiding content visually at breakpoints, ensure accessibility state matches. + +If content should be unavailable: + +```html +<div hidden> +``` + +or CSS `display: none`. + +If content is visually hidden but should remain announced, use a visually hidden utility. + +Do not leave duplicate navigation exposed twice to screen readers unless both are needed and clearly labeled. + +## Canvas Accessibility + +Canvas content is not inherently accessible. + +Provide: + +- Fallback content inside `<canvas>` +- DOM equivalents for interactive elements +- Text alternatives +- Keyboard controls +- ARIA where appropriate +- Data tables for charts +- Separate accessible controls + +```html +<canvas aria-label="Sales chart from January to June"> + Sales increased steadily from January to June. +</canvas> +``` + +For complex or interactive canvas apps, maintain an accessible DOM representation. + +## WebGL And 3D + +WebGL/3D scenes need accessible alternatives. + +Provide: + +- Text descriptions +- Keyboard controls +- Reduced motion mode +- Non-visual alternatives for essential information +- UI controls outside the canvas using semantic HTML +- Avoid color-only distinctions +- Avoid motion-triggering camera behavior where possible + +Do not make critical information available only through 3D visual inspection. + +## Maps + +Maps need accessible alternatives. + +Provide: + +- Searchable list of locations +- Addresses in text +- Directions in text +- Keyboard-accessible controls +- Accessible marker names +- Sufficient contrast +- Zoom controls +- Avoid keyboard traps inside map widgets + +For store locators, a list is usually as important as the map. + +## Charts And Data Visualization + +Charts need: + +- Text summary +- Accessible data table or downloadable data +- Labels +- Legends +- Non-color encodings +- Keyboard-accessible interactive points if interactive +- Announcements for selected data +- Good contrast +- Responsive readability + +Do not rely on hover-only tooltips for essential values. + +## Internationalization And Accessibility + +Accessibility and i18n overlap. + +Use: + +- Correct `lang` +- Logical DOM order +- Native form controls +- Text that can expand +- Direction support with `dir` +- Avoid hard-coded visual positions in instructions +- Avoid concatenated strings that break grammar +- Support translated labels and errors + +For RTL: + +```html +<html lang="ar" dir="rtl"> +``` + +Prefer logical CSS properties: + +```css +margin-inline-start +padding-inline-end +border-start-start-radius +``` + +## Accessible Layout + +Good layout accessibility includes: + +- Clear hierarchy +- Predictable grouping +- Adequate spacing +- No content overlap +- No text clipping +- Logical source order +- Landmarks +- Meaningful headings +- Persistent context in complex flows +- Avoiding sticky elements that obscure focused content + +When using sticky headers, account for focus and anchor offsets. + +```css +:target { + scroll-margin-block-start: 5rem; +} +``` + +Also useful: + +```css +.focus-target { + scroll-margin-block-start: 5rem; +} +``` + +## Tables Versus Cards + +Cards are not a replacement for data tables when users need to compare structured values. + +If using cards for responsive data: + +- Preserve labels for values +- Provide headings for each card +- Keep actions named clearly +- Maintain logical focus order +- Avoid making the entire card clickable if it contains nested controls + +If the whole card is clickable, avoid nested interactive controls or use a different interaction design. + +## Nested Interactive Elements + +Do not nest interactive controls. + +Bad: + +```html +<button> + View order + <a href="/order/1">Details</a> +</button> +``` + +Bad: + +```html +<a href="/product"> + Product + <button>Add to cart</button> +</a> +``` + +Use separate controls. + +## Clickable Cards + +Avoid making large cards clickable when they contain other controls. + +Better: + +```html +<article> + <h2><a href="/product/1">Product name</a></h2> + <button>Add to cart</button> +</article> +``` + +If making the card visually clickable, ensure: + +- One actual link receives focus +- Focus styling covers the card if desired +- No nested interactive elements conflict +- Link text is meaningful + +## Disabled Buttons And Discoverability + +Disabled buttons can hide information from keyboard and screen reader users. + +If users need to understand why an action is unavailable: + +- Keep the control focusable with `aria-disabled` +- Provide explanation +- Or show requirements nearby + +```html +<button type="button" aria-disabled="true" aria-describedby="save-help"> + Save +</button> +<p id="save-help">Add a billing address before saving.</p> +``` + +Then prevent activation in JavaScript. + +## Notifications And Toasts + +Toasts should be accessible. + +Use: + +- Visible text +- Live region announcement +- Reasonable duration +- Manual dismiss if persistent +- No essential information disappearing too quickly +- Focus movement only for urgent/action-required messages +- Accessible close button +- Pause on hover/focus if auto-dismissed + +Example: + +```html +<div role="status">Settings saved.</div> +``` + +For undo: + +```html +<div role="status"> + Message archived. + <button type="button">Undo</button> +</div> +``` + +If the toast contains an interactive control, do not rely only on a passive live region. Ensure keyboard users can reach it. + +## Banners And Cookie Notices + +Cookie banners and consent dialogs should: + +- Be keyboard accessible +- Not trap focus unless modal +- Have clear buttons +- Avoid deceptive patterns +- Expose dialog semantics if modal +- Preserve focus +- Not block zoom or scrolling unnecessarily +- Be dismissible where legally allowed +- Avoid covering focused content + +## Infinite Scroll + +Infinite scroll can harm accessibility. + +If used: + +- Provide pagination or “Load more” alternative +- Preserve focus after loading +- Announce newly loaded content +- Do not move focus unexpectedly +- Allow users to reach the footer +- Update URL/history where appropriate +- Avoid endless automatic loading + +Prefer explicit “Load more” buttons. + +```html +<button type="button">Load more results</button> +``` + +## Virtualized Lists + +Virtualization can break assistive tech expectations. + +Consider: + +- Accurate item counts +- Stable focus +- Proper announcements +- Avoiding removal of focused items +- Preserving find-in-page where important +- Screen reader access to list contents +- Correct `aria-rowcount`, `aria-posinset`, `aria-setsize` only when needed and accurate + +Do not add ARIA metadata that lies about what is actually navigable. + +## Sortable Tables + +Sortable table headers should use buttons. + +```html +<th scope="col"> + <button type="button" aria-sort="ascending"> + Name + </button> +</th> +``` + +Actually, `aria-sort` belongs on the header cell, not the button. + +Better: + +```html +<th scope="col" aria-sort="ascending"> + <button type="button">Name</button> +</th> +``` + +Indicate sort direction visually and programmatically. + +Only one column should usually have `aria-sort` active. + +## Tree Views + +Tree views are complex and should follow ARIA patterns. + +Requirements include: + +- `role="tree"` +- `role="treeitem"` +- `role="group"` +- `aria-expanded` +- Roving tabindex or active descendant +- Arrow key behavior +- Typeahead +- Selection state if selectable +- Clear labeling + +Use a tested component where possible. + +Do not use tree views for ordinary nested navigation unless the interaction pattern is truly needed. + +## Grids + +ARIA grids are application widgets, not layout grids. + +Use native tables for tabular data unless spreadsheet-like interaction is needed. + +ARIA grid requires: + +- Cell focus model +- Arrow key navigation +- Row/column semantics +- Selection semantics if relevant +- Editing behavior if editable +- Screen reader testing + +Do not apply `role="grid"` to CSS grid layouts. + +## Searchable Selects + +For custom searchable selects: + +- Prefer combobox pattern +- Keep input labeled +- Announce expanded state +- Announce result count when helpful +- Support keyboard navigation +- Support escape to close +- Support clear selection +- Handle no results +- Do not trap screen reader virtual cursor unnecessarily +- Ensure selected value is submitted with form + +## Date Pickers + +Date pickers are difficult to make accessible. + +Prefer: + +```html +<input type="date"> +``` + +when acceptable. + +If custom: + +- Provide direct text input alternative +- Use clear date format hints +- Support keyboard navigation +- Expose month/year +- Expose selected date +- Expose today’s date if relevant +- Avoid relying only on visual calendar position +- Allow typing +- Validate clearly +- Support localization + +## File Inputs + +Use native file input when possible. + +```html +<label for="resume">Resume</label> +<input id="resume" type="file" accept=".pdf,.doc,.docx"> +``` + +For custom styling, keep the native input accessible. + +Expose: + +- Accepted formats +- Size limits +- Upload status +- Errors +- File removal controls +- Progress +- Success state + +Do not use drag-and-drop as the only upload method. + +## Captcha + +Avoid inaccessible CAPTCHA. + +If used: + +- Provide accessible alternatives +- Do not rely only on images or audio +- Do not create time pressure +- Ensure keyboard access +- Preserve form data after failure +- Consider privacy and usability impacts + +Prefer risk-based detection and non-interactive approaches. + +## Security And Accessibility + +Security controls should remain accessible. + +Do not: + +- Block paste in password fields +- Break password managers +- Use inaccessible CAPTCHA +- Hide errors +- Force short session timeouts without warning +- Make MFA impossible for assistive tech users + +Use: + +```html +autocomplete="current-password" +autocomplete="new-password" +autocomplete="one-time-code" +``` + +## Printing And Accessibility + +Print styles should preserve readable content. + +For accessibility: + +- Do not hide essential content in print +- Expand URLs where useful +- Preserve sufficient contrast +- Avoid printing interactive-only instructions +- Ensure form summaries are readable + +## Accessibility In CSS + +CSS can help or harm accessibility. + +Good: + +```css +:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; +} + +@media (prefers-reduced-motion: reduce) { + .animated { + animation: none; + } +} +``` + +Avoid: + +```css +outline: none; +``` + +Avoid text hiding hacks unless intentional and tested. + +Avoid fixed heights with dynamic text. + +Avoid low-contrast placeholder text. + +Avoid `cursor: pointer` on non-interactive elements as a substitute for real semantics. + +Use logical properties for better writing-mode support. + +## Accessibility In JavaScript + +JavaScript should enhance accessibility, not replace native behavior unnecessarily. + +Principles: + +- Listen to both pointer and keyboard events when needed +- Use `click` for activation where possible because keyboard activation of native controls fires click +- Avoid keypress; use `keydown` or `keyup` +- Do not override browser shortcuts unnecessarily +- Do not trap focus except in true modals +- Clean up event listeners +- Restore focus after DOM changes +- Keep ARIA state synchronized with visual state +- Avoid fake disabled states +- Avoid fake links/buttons +- Handle async loading with status updates +- Preserve user input +- Avoid unexpected context changes + +Example disclosure: + +```js +const button = document.querySelector("[aria-controls='filters']"); +const panel = document.getElementById(button.getAttribute("aria-controls")); + +button.addEventListener("click", () => { + const expanded = button.getAttribute("aria-expanded") === "true"; + + button.setAttribute("aria-expanded", String(!expanded)); + panel.hidden = expanded; +}); +``` + +## Keyboard Event Handling + +Use standard keys. + +```js +element.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + close(); + } +}); +``` + +Use `event.key`, not deprecated keyCode. + +Common keys: + +```js +"Enter" +" " +"Escape" +"ArrowUp" +"ArrowDown" +"ArrowLeft" +"ArrowRight" +"Home" +"End" +"PageUp" +"PageDown" +"Tab" +``` + +Do not prevent default unless necessary. + +If implementing custom button behavior, both `Enter` and `Space` should work. But prefer native `<button>`. + +## Roving Tabindex + +Composite widgets often use roving tabindex. + +Pattern: + +```html +<div role="toolbar" aria-label="Text formatting"> + <button tabindex="0">Bold</button> + <button tabindex="-1">Italic</button> + <button tabindex="-1">Underline</button> +</div> +``` + +Arrow keys move focus and update tabindex. + +Use for: + +- Tabs +- Toolbars +- Menus +- Radio-like custom groups +- Grids +- Listboxes in some patterns + +Only one item in the composite is in the tab order. + +## `aria-activedescendant` + +`aria-activedescendant` lets focus remain on a container or input while indicating an active child. + +Common for: + +- Comboboxes +- Listboxes +- Grids +- Autocomplete + +Requirements: + +- Container has DOM focus +- Active child has an `id` +- `aria-activedescendant` references that `id` +- Visual active state matches ARIA state +- The active item is scrolled into view + +```html +<input + role="combobox" + aria-expanded="true" + aria-controls="results" + aria-activedescendant="option-2" +> + +<ul id="results" role="listbox"> + <li id="option-1" role="option">Alpha</li> + <li id="option-2" role="option">Beta</li> +</ul> +``` + +## Announcing Dynamic Counts + +For dynamic results: + +```html +<p role="status" aria-live="polite"> + 12 results found. +</p> +``` + +Avoid announcing huge content updates. Announce summaries. + +## Accessible State Synchronization + +Whenever UI state changes, synchronize: + +- DOM visibility +- ARIA state +- CSS state +- Focusability +- Keyboard behavior +- Screen reader exposure + +Example: + +- `aria-expanded="true"` +- Panel is visible +- Panel is focusable if needed +- Trigger styling shows open state + +Do not let visual state and ARIA state diverge. + +## Progressive Enhancement + +Build baseline functionality with HTML. + +Then enhance with CSS and JS. + +Good baseline: + +```html +<form action="/search" method="get"> + <label for="q">Search</label> + <input id="q" name="q"> + <button>Search</button> +</form> +``` + +Enhanced behavior can add autocomplete, dynamic results, and live announcements. + +If JavaScript fails, critical tasks should still work when practical. + +## Graceful Degradation + +When using newer APIs: + +- Provide fallback behavior +- Feature-detect +- Avoid user-agent sniffing +- Keep semantics valid without enhancement +- Do not require unsupported APIs for critical flows + +Example: + +```js +if ("showModal" in HTMLDialogElement.prototype) { + dialog.showModal(); +} else { + openFallbackDialog(); +} +``` + +## Modern APIs Useful For Accessibility + +Modern browser features I would consider useful: + +- `<dialog>` +- `inert` +- Popover API +- `:focus-visible` +- `:has()` for styling state without extra JS +- `prefers-reduced-motion` +- `prefers-color-scheme` +- `prefers-contrast` +- `forced-colors` +- CSS logical properties +- `accent-color` +- `color-scheme` +- `scroll-margin` +- `scroll-padding` +- `text-wrap` +- `ResizeObserver` +- `IntersectionObserver` +- `MutationObserver` +- `AbortController` for listener cleanup +- Form validation APIs +- Constraint validation +- Custom elements with careful semantic design +- ElementInternals for form-associated custom elements where appropriate +- `ariaNotify` if/where available as progressive enhancement only +- View Transitions API only with reduced-motion safeguards + +Use cutting-edge APIs only when fallback preserves accessibility. + +## Constraint Validation API + +Native validation can help but often needs custom messaging for usability. + +```js +const input = document.querySelector("#email"); + +if (!input.validity.valid) { + input.setCustomValidity("Enter a valid email address."); +} else { + input.setCustomValidity(""); +} +``` + +Methods/properties: + +```js +input.validity +input.validationMessage +input.checkValidity() +input.reportValidity() +input.setCustomValidity() +form.requestSubmit() +``` + +Use `requestSubmit()` rather than manually dispatching submit when you want native submit behavior. + +```js +form.requestSubmit(); +``` + +## Custom Elements + +Custom elements need extra care. + +Default custom elements have no useful semantics. + +For accessible custom elements: + +- Prefer wrapping native elements internally +- Expose labels +- Forward focus appropriately +- Reflect state to ARIA where needed +- Support keyboard interaction +- Support forms if form-associated +- Test with screen readers +- Avoid closed shadow roots when accessibility/testing requires inspection +- Use `delegatesFocus` carefully +- Ensure accessible names cross shadow boundaries as expected + +Form-associated custom elements can use `ElementInternals`. + +```js +class MyInput extends HTMLElement { + static formAssociated = true; + + constructor() { + super(); + this.internals = this.attachInternals(); + } +} +``` + +Use only when necessary; native form controls are usually better. + +## Shadow DOM + +Shadow DOM can complicate accessibility. + +Consider: + +- Label association +- Form participation +- Focus delegation +- Accessible names +- ARIA relationships across shadow boundaries +- Testing support +- Exposing parts for focus styles +- Slot content semantics + +Prefer native controls inside shadow DOM and test thoroughly. + +Do not assume ARIA references always work across shadow boundaries. + +## Web Components + +Accessible web components should: + +- Have documented keyboard behavior +- Use semantic internal markup +- Expose accessible names +- Reflect important states +- Support disabled/readonly where applicable +- Support forms when relevant +- Provide focus methods when useful +- Avoid surprising tab order +- Work with screen readers +- Support high contrast and reduced motion +- Avoid trapping users inside shadow DOM + +## Native Form APIs + +Use modern form APIs: + +```js +const data = new FormData(form); +``` + +```js +form.requestSubmit(); +``` + +```js +submitter = event.submitter; +``` + +```js +button.form +button.formAction +button.formMethod +``` + +These preserve native form behavior and accessibility better than custom submission plumbing. + +## FormData + +Use `FormData` for form serialization. + +```js +form.addEventListener("submit", (event) => { + event.preventDefault(); + + const formData = new FormData(form); +}); +``` + +This respects native controls and names. + +## Labels And Programmatic Relationships + +Use explicit labels when possible. + +```html +<label for="first-name">First name</label> +<input id="first-name" name="firstName"> +``` + +Implicit labels are valid: + +```html +<label> + First name + <input name="firstName"> +</label> +``` + +Explicit labels are often easier for layout and testing. + +Multiple labels can reference the same control, but keep names clear. + +## Group Labels + +Use `fieldset` and `legend` for grouped form controls. + +```html +<fieldset> + <legend>Payment method</legend> + ... +</fieldset> +``` + +Use `role="group"` with `aria-labelledby` only when native grouping is unsuitable. + +```html +<div role="group" aria-labelledby="filters-heading"> + <h2 id="filters-heading">Filters</h2> + ... +</div> +``` + +## Autocomplete + +Use correct autocomplete attributes. + +Examples: + +```html +autocomplete="name" +autocomplete="given-name" +autocomplete="family-name" +autocomplete="email" +autocomplete="username" +autocomplete="current-password" +autocomplete="new-password" +autocomplete="one-time-code" +autocomplete="organization" +autocomplete="street-address" +autocomplete="address-line1" +autocomplete="address-level2" +autocomplete="address-level1" +autocomplete="postal-code" +autocomplete="country" +autocomplete="tel" +autocomplete="cc-name" +autocomplete="cc-number" +autocomplete="cc-exp" +autocomplete="cc-csc" +``` + +Autocomplete improves usability for many users, including users with motor, cognitive, and memory-related disabilities. + +## Input Modes + +Use `inputmode` to improve virtual keyboards. + +```html +<input inputmode="numeric"> +<input inputmode="decimal"> +<input inputmode="tel"> +<input inputmode="email"> +<input inputmode="url"> +<input inputmode="search"> +``` + +Do not use `type="number"` for values that are not numeric quantities, such as ZIP codes or credit card numbers. + +For numeric-looking strings: + +```html +<input inputmode="numeric" autocomplete="postal-code"> +``` + +## Pattern Attribute + +Use `pattern` carefully. + +Provide visible format instructions and clear errors. + +```html +<label for="code">Security code</label> +<p id="code-help">Enter 3 digits.</p> +<input + id="code" + name="code" + inputmode="numeric" + pattern="[0-9]{3}" + aria-describedby="code-help" +> +``` + +Do not rely on pattern alone for explanation. + +## Native Inputs + +Use the right input for the job. + +But be careful: + +- `type="number"` has spinbutton semantics and may be bad for credit cards, ZIP codes, account numbers +- `type="date"` support and localization vary but is often acceptable +- `type="search"` provides useful semantics and platform behavior +- `type="email"` and `type="url"` provide validation and keyboards +- `type="tel"` is good for phone-like input but does not validate phone numbers + +## Editable Content + +Avoid `contenteditable` for form-like input unless necessary. + +If used: + +- Provide role and label +- Ensure keyboard support +- Announce formatting state +- Preserve focus +- Sanitize content +- Support paste +- Provide alternatives for toolbar commands +- Test with screen readers + +Example: + +```html +<div + contenteditable="true" + role="textbox" + aria-multiline="true" + aria-label="Message" +></div> +``` + +Native `<textarea>` is usually better. + +## Rich Text Editors + +Rich text editors require substantial accessibility support. + +Needed: + +- Labeled editing area +- Keyboard shortcuts with discoverable alternatives +- Toolbar buttons with names and pressed states +- Semantic output +- Formatting state announcements +- Focus management between toolbar and editor +- Screen reader testing +- Paste handling +- Undo/redo +- Accessible help + +Use mature editor libraries with accessibility support rather than building from scratch. + +## Focus Traps + +Use focus traps only for modal contexts. + +Requirements: + +- Initial focus enters modal +- `Tab` wraps within modal +- `Shift+Tab` wraps backward +- `Escape` closes if allowed +- Focus returns to invoker +- Background is inert +- Trap deactivates on close +- Dynamic content is handled + +Do not trap focus in non-modal sidebars, dropdowns, or pages. + +## Scroll Management + +Avoid scroll behavior that disorients users. + +Use: + +```css +html { + scroll-behavior: smooth; +} +``` + +only if respecting reduced motion. + +```css +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +} +``` + +For focus targets under sticky headers: + +```css +:target { + scroll-margin-block-start: 5rem; +} +``` + +Do not hijack scrolling. + +## Anchors And Fragment Navigation + +Anchor targets should not be obscured by sticky headers. + +Use `scroll-margin`. + +```css +[id] { + scroll-margin-block-start: 5rem; +} +``` + +If moving focus to a heading after navigation, make it programmatically focusable: + +```html +<h1 tabindex="-1">Results</h1> +``` + +## Keyboard Shortcuts + +Keyboard shortcuts should: + +- Not conflict with browser or assistive tech shortcuts +- Be documented +- Be customizable or disableable if extensive +- Not require single-character shortcuts without modifier unless appropriate +- Not be the only way to perform an action +- Respect focused form fields + +Use `aria-keyshortcuts` only when helpful and accurate. + +```html +<button aria-keyshortcuts="Control+S">Save</button> +``` + +## Accessible Names For Repeated Controls + +Repeated controls need distinguishable names. + +Bad: + +```html +<button>Edit</button> +<button>Edit</button> +<button>Edit</button> +``` + +Better: + +```html +<button aria-label="Edit billing address">Edit</button> +<button aria-label="Edit shipping address">Edit</button> +``` + +Or include visible context: + +```html +<h2>Billing address</h2> +<button>Edit billing address</button> +``` + +## Images Of Text + +Avoid images of text. + +Use real text so users can: + +- Zoom +- Select +- Translate +- Use custom styles +- Get proper contrast +- Have it read by screen readers + +Exceptions include logos, but provide text alternatives. + +## Typography + +Accessible typography practices: + +- Use readable font sizes +- Use adequate line height +- Avoid very thin fonts +- Avoid justified text for long passages +- Avoid all caps for long passages +- Keep line length reasonable +- Ensure text can resize +- Avoid text over busy images unless contrast is guaranteed +- Do not encode meaning only with font style or color + +## Contrast Over Images + +Text over images needs reliable contrast. + +Use: + +- Solid overlays +- Scrims +- Text shadows only as supplemental +- Responsive testing +- Avoid placing text over visually complex areas + +Do not assume a gradient overlay always preserves contrast across image crops. + +## Dark Mode + +Dark mode must maintain contrast and state clarity. + +Use: + +```css +:root { + color-scheme: light dark; +} +``` + +Test: + +- Text contrast +- Focus indicators +- Borders +- Disabled states +- Error colors +- Charts +- Images/icons +- Form controls + +Do not invert images blindly. + +## `accent-color` + +Use `accent-color` to style native controls accessibly. + +```css +:root { + accent-color: #005fcc; +} +``` + +Ensure chosen accent has sufficient contrast in relevant states. + +## `color-scheme` + +Use `color-scheme` to let browsers render native controls appropriately. + +```css +:root { + color-scheme: light dark; +} +``` + +Or: + +```css +:root { + color-scheme: light; +} +``` + +Use carefully with custom themes. + +## Reduced Transparency + +If using blur/transparency, support reduced transparency where available. + +```css +@media (prefers-reduced-transparency: reduce) { + .frosted { + backdrop-filter: none; + background: Canvas; + } +} +``` + +Also ensure fallback without this media query is still readable. + +## ARIA Live Versus Focus + +Use live regions for passive updates. + +Move focus for: + +- New route/page +- Opened modal +- Error summary after failed submit +- Newly inserted content requiring immediate interaction +- Destructive confirmation + +Do not move focus for: + +- Routine save success +- Result count updates +- Background polling +- Toasts that do not need action + +## Hiding Decorative Content + +For decorative icons: + +```html +<svg aria-hidden="true" focusable="false">...</svg> +``` + +For decorative images: + +```html +<img src="decorative.jpg" alt=""> +``` + +For CSS background images, no alt is needed; ensure they are decorative or duplicated by text. + +## Accessible Cards And Articles + +Use semantic grouping. + +```html +<article> + <h2><a href="/posts/1">Post title</a></h2> + <p>Summary...</p> +</article> +``` + +For repeated items: + +- Use headings +- Make primary action clear +- Avoid too many tab stops if not needed +- Keep action labels specific +- Preserve logical reading order + +## Regions + +Use `role="region"` sparingly. + +A region should have an accessible name. + +```html +<section aria-labelledby="activity-title"> + <h2 id="activity-title">Recent activity</h2> + ... +</section> +``` + +Too many named regions clutter landmark navigation. + +## Forms In Modals + +Forms inside modals need: + +- Dialog label +- Initial focus +- Form labels +- Error handling inside dialog +- Focus to errors +- Non-destructive cancel +- Submit button +- Escape behavior considered carefully +- Focus restoration on close + +If submit succeeds and dialog closes, announce success outside or restore focus to meaningful location. + +## Destructive Actions + +For destructive actions: + +- Use specific labels +- Avoid ambiguous “OK” +- Confirm when consequence is serious +- Provide undo where possible +- Make danger visually and textually clear +- Do not rely on red alone +- Ensure confirmation dialog is accessible + +```html +<button type="button">Delete invoice</button> +``` + +Not: + +```html +<button type="button">Yes</button> +``` + +unless context is very clear. + +## Undo + +Undo is often better than confirmation for low-risk actions. + +Accessible undo: + +- Announce action +- Provide reachable Undo button +- Keep undo available long enough +- Do not auto-dismiss too quickly +- Make status visible + +```html +<div role="status"> + Item archived. + <button type="button">Undo</button> +</div> +``` + +## Breadcrumbs + +Use nav and ordered list. + +```html +<nav aria-label="Breadcrumb"> + <ol> + <li><a href="/">Home</a></li> + <li><a href="/docs">Docs</a></li> + <li aria-current="page">Accessibility</li> + </ol> +</nav> +``` + +Do not make current page a link unless useful. + +## Pagination + +Pagination should have a nav label and current page. + +```html +<nav aria-label="Pagination"> + <a href="?page=1">Page 1</a> + <a href="?page=2" aria-current="page">Page 2</a> + <a href="?page=3">Page 3</a> +</nav> +``` + +Icon-only previous/next buttons need names. + +```html +<a href="?page=1" aria-label="Previous page">...</a> +``` + +## Steppers And Multi-Step Forms + +Use: + +- Clear step title +- Progress indication +- Current step state +- Back/next buttons +- Error summary per step +- Preserve data +- Allow review before submit +- Avoid timeouts +- Focus management on step changes + +Example: + +```html +<nav aria-label="Checkout progress"> + <ol> + <li><a href="/cart">Cart</a></li> + <li aria-current="step">Shipping</li> + <li>Payment</li> + </ol> +</nav> +``` + +## Current Item State + +Use `aria-current` for current item in a set. + +Values include: + +```html +aria-current="page" +aria-current="step" +aria-current="location" +aria-current="date" +aria-current="time" +aria-current="true" +``` + +Use for navigation, breadcrumbs, calendars, steppers. + +Do not use `aria-selected` unless the widget pattern supports selection. + +## Selection Versus Current Versus Pressed + +Use the right state: + +- `aria-current`: current item in navigation or set +- `aria-selected`: selected option/tab/gridcell in selectable widget +- `aria-pressed`: toggle button pressed state +- `aria-checked`: checkbox/radio/switch checked state +- `aria-expanded`: disclosure expanded state + +Do not interchange them casually. + +## Status In Buttons + +Loading button: + +```html +<button type="submit" disabled> + Saving... +</button> +``` + +If using spinner: + +```html +<button type="submit" disabled> + <span aria-hidden="true" class="spinner"></span> + Saving... +</button> +``` + +Do not use spinner alone. + +## Naming Close Buttons + +Close buttons should be specific when context is ambiguous. + +```html +<button type="button" aria-label="Close dialog"> + <svg aria-hidden="true">...</svg> +</button> +``` + +For multiple dismiss buttons: + +```html +aria-label="Dismiss notification" +aria-label="Close filters panel" +``` + +## Accessible Notifications Count + +For badges: + +```html +<a href="/messages"> + Messages + <span aria-label="3 unread">3</span> +</a> +``` + +Or: + +```html +<a href="/messages" aria-label="Messages, 3 unread"> + Messages <span aria-hidden="true">3</span> +</a> +``` + +Avoid unlabeled numeric badges. + +## Abbreviations + +Expand abbreviations where needed. + +```html +<abbr title="World Wide Web Consortium">W3C</abbr> +``` + +Do not overuse `abbr`. Plain text explanations are often clearer. + +## Data Formats + +Provide format hints before input. + +```html +<label for="dob">Date of birth</label> +<p id="dob-help">Use MM/DD/YYYY.</p> +<input id="dob" aria-describedby="dob-help" inputmode="numeric"> +``` + +Do not reveal format requirements only after an error. + +## Currency And Numbers + +Use clear visible formatting and accessible text when needed. + +Avoid ambiguous numbers. + +For screen readers, normal text is usually fine: + +```html +<p>Total: $1,234.56</p> +``` + +For charts or compact UI, ensure labels explain units. + +## Time And Dates + +Use `<time>`. + +```html +<time datetime="2026-05-13">May 13, 2026</time> +``` + +Use machine-readable datetime for parsing and assistive tech compatibility. + +Avoid ambiguous dates like `05/06/26` when audience may vary. + +## Tables With Actions + +For rows with repeated actions, names should include row context. + +```html +<button aria-label="Edit order 1234">Edit</button> +``` + +Or use visible text: + +```html +<button>Edit order 1234</button> +``` + +Avoid repeated unlabeled “Edit” buttons in tables. + +## ARIA Describedby Chaining + +Multiple descriptions can be referenced. + +```html +<input aria-describedby="password-help password-error"> +``` + +Keep order meaningful. + +Do not overload descriptions with too much content. + +## Error Summary Links + +Error summary links should point to fields. + +```html +<a href="#email">Enter a valid email address.</a> +``` + +When clicked, focus should reach the relevant input. Native anchor behavior may be enough if the input has the ID and is focusable. + +## CSS `display: contents` + +Be cautious with `display: contents`. + +It can affect accessibility semantics in some browser/assistive technology combinations, especially on semantic elements. + +Avoid using it on elements whose semantics matter unless tested. + +## CSS `visibility` + +`visibility: hidden` hides from users and accessibility tree but preserves layout space. + +Use intentionally. + +Do not hide focusable content with `visibility: hidden`. + +## `opacity: 0` + +`opacity: 0` visually hides but does not remove from accessibility tree or focus order. + +This can create invisible focusable controls. + +If hiding content from everyone, use `hidden` or `display: none`. + +If visually hiding but keeping accessible, use a tested visually-hidden pattern. + +## Pointer Events + +`pointer-events: none` does not remove elements from keyboard focus or accessibility tree. + +Do not use it as a disabled state by itself. + +If disabled, also handle semantics and keyboard behavior. + +## ARIA Controls + +`aria-controls` indicates a relationship but does not create behavior. + +```html +<button aria-controls="panel" aria-expanded="false">Details</button> +``` + +You still need JavaScript to show/hide the panel and update state. + +## ARIA Owns + +Avoid `aria-owns` unless necessary. + +It can reorder the accessibility tree and create confusing behavior. + +Prefer DOM order. + +## ARIA Role Conflicts + +Do not put conflicting roles on native elements. + +Bad: + +```html +<a href="/home" role="button">Home</a> +``` + +If it navigates, it is a link. If it acts, use button. + +## Accessible Name From Content + +Buttons and links derive names from content. + +This is best: + +```html +<button>Continue to payment</button> +``` + +Avoid overriding visible text with a different `aria-label`, because speech input users may say the visible text. + +## Speech Recognition + +Accessible names should match visible labels where possible. + +Bad: + +```html +<button aria-label="Submit order">Buy now</button> +``` + +A speech recognition user may say “Click Buy now” but the accessible name is “Submit order.” + +Prefer: + +```html +<button>Buy now</button> +``` + +or: + +```html +<button>Submit order</button> +``` + +## Voice Control + +Voice control benefits from: + +- Visible labels matching accessible names +- Real buttons and links +- Unique control names +- Avoiding custom controls without semantics +- Avoiding hidden names that differ from visible names + +## Screen Magnification + +Screen magnifier users benefit from: + +- Visible focus +- No unexpected focus movement +- Consistent layout +- Responsive design +- Avoiding content that appears far from trigger +- Avoiding hover-only content +- Keeping errors near fields +- Scroll-margin for focused content + +## Screen Reader Considerations + +Screen reader users benefit from: + +- Semantic structure +- Headings +- Landmarks +- Labels +- Meaningful links +- Proper form errors +- Table headers +- Live region restraint +- Predictable focus +- Avoiding redundant verbosity +- Avoiding ARIA misuse + +Do not assume all screen reader users are blind. Do not make “screen reader only” UX diverge from visible UX unnecessarily. + +## Automated Testing + +Use automated accessibility testing as a baseline. + +Common tools: + +- axe +- Lighthouse +- Pa11y +- jest-axe +- Testing Library accessibility queries +- Playwright accessibility-oriented checks +- eslint-plugin-jsx-a11y for React +- Storybook accessibility add-ons + +Automated tools catch many issues, such as: + +- Missing labels +- Missing alt text +- Color contrast in many cases +- Invalid ARIA +- Missing button names +- Duplicate IDs +- Landmark issues +- Heading problems + +Automated tools do not fully catch: + +- Keyboard traps +- Incorrect focus management +- Bad screen reader UX +- Meaningful alt quality +- Logical reading order +- Cognitive complexity +- Custom widget correctness +- All color contrast situations +- Zoom/reflow problems +- Motion sensitivity +- Usability of flows + +## Manual Testing + +Manual checks should include: + +- Keyboard-only navigation +- Screen reader smoke testing +- Browser zoom to 200% +- Mobile viewport +- Touch interaction +- High contrast / forced colors +- Reduced motion +- Form errors +- Modal behavior +- Route changes +- Dynamic updates +- Landmark/headings navigation +- Color contrast +- Focus visibility + +Keyboard checklist: + +- Can I reach every interactive element? +- Can I see focus? +- Is focus order logical? +- Can I activate controls? +- Can I escape overlays? +- Is there any trap? +- Does focus return after closing modals? +- Are hidden controls skipped? + +## Screen Reader Testing + +Common combinations: + +- NVDA with Firefox or Chrome on Windows +- JAWS with Chrome or Edge on Windows +- VoiceOver with Safari on macOS +- VoiceOver with Safari on iOS +- TalkBack with Chrome on Android + +Test at least one realistic combination when working on complex interactions. + +Screen reader checks: + +- Page title is useful +- Headings are meaningful +- Landmarks are present +- Controls have names +- States are announced +- Errors are announced +- Dynamic updates are reasonable +- Custom widgets follow expected behavior +- Reading order matches visual order + +## Testing Library Practices + +Prefer queries that reflect accessibility. + +Good: + +```js +screen.getByRole("button", { name: /save/i }); +screen.getByLabelText(/email/i); +screen.getByRole("heading", { name: /settings/i }); +``` + +Avoid relying only on test IDs for interactive controls. + +Testing by role and name encourages accessible markup. + +## React Accessibility + +React accessibility defaults: + +- Use `htmlFor` instead of `for` +- Use `className` +- Use semantic components +- Keep stable IDs with `useId` +- Do not create inaccessible custom buttons +- Manage focus after conditional rendering +- Clean up effects +- Keep ARIA state in sync with component state +- Avoid rendering invalid DOM nesting +- Use fragments carefully to preserve semantics + +Example: + +```jsx +const id = useId(); + +return ( + <> + <label htmlFor={id}>Email</label> + <input id={id} type="email" autoComplete="email" /> + </> +); +``` + +React event handlers should not make non-interactive elements interactive without adding semantics and keyboard support. + +Bad: + +```jsx +<div onClick={onClose}>Close</div> +``` + +Good: + +```jsx +<button type="button" onClick={onClose}>Close</button> +``` + +## Vue Accessibility + +Vue accessibility defaults: + +- Use native elements +- Bind ARIA state accurately +- Preserve labels and IDs +- Avoid clickable divs +- Manage focus after conditional rendering +- Be careful with `v-if` removing focused content +- Use `nextTick` before focusing newly rendered elements +- Ensure components pass attributes to accessible elements + +Example: + +```vue +<label :for="id">Email</label> +<input :id="id" type="email" autocomplete="email"> +``` + +## Angular Accessibility + +Angular accessibility defaults: + +- Use native controls +- Use Angular CDK a11y utilities where useful +- Use `cdkTrapFocus` for modals when appropriate +- Manage focus with CDK FocusMonitor when useful +- Bind ARIA states accurately +- Preserve form labels and validation messages +- Avoid custom controls unless implementing ControlValueAccessor accessibly + +Angular Material components generally handle many accessibility concerns but still need correct labels and usage. + +## Svelte Accessibility + +Svelte accessibility defaults: + +- Pay attention to compiler a11y warnings +- Use native controls +- Avoid click handlers on non-interactive elements +- Manage focus after conditional rendering +- Bind ARIA state accurately +- Ensure component abstractions preserve semantic markup + +## Component Libraries + +Component libraries do not guarantee accessibility. + +Still verify: + +- Correct usage +- Labels provided +- Keyboard behavior +- Focus management +- Color contrast after theming +- Screen reader behavior +- Error messages +- Responsive behavior +- Disabled states +- High contrast mode +- Reduced motion + +A library can provide accessible primitives, but product implementation can still break them. + +## Design Systems + +Design systems should define: + +- Accessible color tokens +- Focus styles +- Form field patterns +- Error patterns +- Modal behavior +- Keyboard conventions +- Component semantics +- Reduced motion behavior +- Touch target sizes +- Testing expectations +- Documentation for accessible names and labels + +A component API should make accessible usage easy and inaccessible usage hard. + +## API Design For Components + +Accessible component APIs should require or infer names. + +Good: + +```jsx +<IconButton aria-label="Close" icon={<CloseIcon />} /> +``` + +Better when visible label exists: + +```jsx +<Button icon={<SaveIcon />}>Save</Button> +``` + +Avoid APIs that hide semantics: + +```jsx +<Box clickable onClick={...}> +``` + +Prefer components that render the correct element: + +```jsx +<Button> +<Link> +<Heading> +<Field> +<Dialog> +``` + +Allow polymorphism carefully. If using `as`, ensure semantics still match behavior. + +## Accessibility And Type Systems + +Type systems can enforce accessibility. + +Examples: + +- Require `aria-label` for icon-only button components +- Require labels for fields +- Restrict heading levels +- Model button vs link props separately +- Prevent `href` and `onClick` ambiguity +- Require dialog title +- Require alt text for image components, with explicit decorative handling + +Example idea: + +```ts +type IconButtonProps = + | { children: string; "aria-label"?: never } + | { children?: never; "aria-label": string }; +``` + +## Linting + +Use accessibility linting. + +Common checks: + +- Alt text +- Label association +- Valid ARIA roles +- Valid ARIA attributes +- No autofocus +- No positive tabindex +- Click handlers need keyboard handlers +- Interactive elements must be focusable +- No redundant roles +- Media captions +- Iframe title + +Do not silence lint rules without understanding the accessibility consequence. + +## Iframes + +Iframes need titles. + +```html +<iframe + title="Payment form" + src="..." +></iframe> +``` + +The title should describe the iframe’s purpose. + +Ensure embedded content itself is accessible. + +Avoid keyboard traps between host page and iframe. + +## Third-Party Widgets + +Third-party widgets can create accessibility problems. + +Check: + +- Keyboard access +- Focus traps +- Screen reader labels +- Color contrast +- Resize/zoom behavior +- Captcha accessibility +- Cookie banners +- Chat widgets +- Payment forms +- Maps +- Analytics overlays +- Ads + +Load third-party widgets in a way that does not block core tasks. + +## Chat Widgets + +Chat widgets should: + +- Be keyboard accessible +- Have named controls +- Not steal focus on load +- Not spam live regions +- Not cover important controls +- Respect reduced motion +- Be dismissible +- Preserve focus +- Work at zoom +- Have accessible unread indicators + +## Ads And Injected Content + +Ads should not: + +- Trap focus +- Autoplay audio +- Flash +- Obscure content +- Break reading order +- Insert unexpected focusable elements +- Hijack keyboard shortcuts + +## Consent And Privacy UI + +Consent UI should avoid deceptive or inaccessible patterns. + +Controls must be: + +- Keyboard reachable +- Clearly labeled +- Equally accessible for accept/reject/customize +- Focus visible +- Screen reader understandable +- Not dependent on color alone + +## Mobile Accessibility + +Mobile accessibility considerations: + +- Touch target size +- Screen reader gestures +- Orientation changes +- Zoom support +- Dynamic type/text scaling +- Focus order +- Keyboard support for external keyboards +- Proper input types +- Avoid fixed overlays that obscure content +- Avoid hover-only interactions +- Support reduced motion +- Use native controls where possible + +Test with VoiceOver iOS and TalkBack Android for complex flows. + +## Orientation + +Do not lock orientation unless essential. + +Layouts should work in portrait and landscape. + +If orientation is essential, provide an explanation or alternative. + +## Device Sensors + +Do not require device motion as the only input. + +If using shake, tilt, or motion: + +- Provide controls +- Ask permission where required +- Respect reduced motion +- Avoid motion sickness triggers + +## Haptics And Sound + +Do not rely only on sound or haptics. + +Provide visual and programmatic feedback. + +Allow users to mute or disable non-essential sound. + +## Offline And Network Errors + +Network errors should be accessible. + +Use: + +- Clear visible messages +- Retry buttons +- Status announcements +- Preserve user data +- Avoid infinite spinners +- Focus management for blocking errors + +```html +<div role="alert"> + Could not save changes. Check your connection and try again. +</div> +``` + +## Progressive Web Apps + +PWAs should ensure: + +- Install prompts are accessible +- Offline states are clear +- App shell has landmarks +- Route changes manage focus +- Push notifications are understandable +- Badges have accessible equivalents +- Splash/loading screens do not trap users + +## Notifications API + +Browser notifications should: + +- Be opt-in +- Have clear permission context +- Not be required for core functionality +- Have meaningful titles and bodies +- Avoid excessive frequency +- Provide in-app accessible equivalents for important notifications + +## Permissions + +Permission prompts need context. + +Before requesting: + +- Explain why permission is needed +- Trigger from user action +- Provide fallback if denied +- Do not repeatedly nag + +Relevant permissions: + +- Camera +- Microphone +- Location +- Notifications +- Clipboard +- Sensors + +## Clipboard + +Clipboard interactions should be accessible. + +```html +<button type="button">Copy invite link</button> +``` + +After copy: + +```html +<div role="status">Invite link copied.</div> +``` + +Do not copy automatically without user action. + +## Camera And Microphone + +Media capture flows need: + +- Labeled controls +- Permission explanation +- Keyboard-accessible recording controls +- Visible and announced recording state +- Captions/transcripts where relevant +- Alternatives to camera-only workflows + +## Location + +Location-based features should: + +- Explain why location is needed +- Provide manual entry alternative +- Handle denial clearly +- Avoid making location mandatory unless essential + +## Download Links + +Indicate file type and size when useful. + +```html +<a href="/report.pdf">Download annual report PDF, 2 MB</a> +``` + +Ensure PDFs or downloaded documents are accessible. + +## PDFs And Documents + +Linked documents should be accessible. + +Accessible PDFs need: + +- Tags +- Reading order +- Headings +- Alt text +- Table headers +- Document language +- Title +- Form labels if forms + +Where possible, provide HTML alternatives. + +## Email Accessibility + +HTML emails should: + +- Use semantic structure where supported +- Include alt text +- Maintain contrast +- Work with zoom +- Avoid image-only content +- Use meaningful link text +- Support dark mode carefully +- Use accessible buttons as links +- Keep layout readable + +## Performance And Accessibility + +Performance affects accessibility. + +Slow interfaces can harm users with: + +- Cognitive disabilities +- Motor disabilities +- Screen readers +- Magnification +- Older devices +- Limited networks + +Good practices: + +- Avoid long main-thread blocking +- Show accessible loading states +- Preserve input during async work +- Avoid layout shifts +- Use skeletons carefully +- Do not remove focused elements unexpectedly +- Optimize images and scripts + +## Layout Shift + +Unexpected layout shift can cause users to lose place or activate wrong controls. + +Prevent with: + +- Reserved space for images/media +- Stable button positions +- Avoid inserting content above current focus +- Avoid late-loading banners that push content +- Proper dimensions +- Careful font loading + +## Font Loading + +Font loading should not make text invisible for too long. + +Use sensible `font-display`, commonly: + +```css +@font-face { + font-family: "Inter"; + src: url("/inter.woff2") format("woff2"); + font-display: swap; +} +``` + +Ensure fallback fonts do not break layout. + +## Skeleton Screens + +Skeletons should: + +- Not be announced as real content +- Not trap focus +- Be replaced cleanly +- Respect reduced motion +- Have actual loading text/status where needed + +Use: + +```html +<div role="status">Loading profile...</div> +``` + +## Disabled Loading States + +When loading disables a form: + +- Announce loading +- Keep users informed +- Avoid clearing data +- Restore focus if needed +- Prevent duplicate submission +- Allow cancellation for long operations where possible + +## Accessibility In Design Review + +Review designs for: + +- Heading structure +- Labels +- Error states +- Focus states +- Keyboard flows +- Modal behavior +- Touch target sizes +- Contrast +- Motion +- Text scaling +- Empty/loading/error states +- Responsive behavior +- Screen reader names for icon-only controls +- Destructive action recovery +- Non-color indicators + +Design files often omit focus and error states; implementation should not. + +## Common Anti-Patterns + +Avoid: + +- Clickable `div`s +- Placeholder-only labels +- Removing focus outlines +- Positive tabindex +- `aria-hidden` on focusable content +- `role="button"` on non-keyboard elements +- Color-only errors +- Icon-only buttons without names +- Links without `href` +- Buttons used for navigation +- Links used for actions +- Modal without focus management +- Hover-only menus +- Auto-advancing carousels without pause +- Custom selects without keyboard support +- Inaccessible drag-and-drop +- Empty alt on meaningful images +- Redundant verbose alt on decorative images +- Invalid ARIA attributes +- Mismatched ARIA state and visual state +- Screen-reader-only instructions that sighted users also need +- Unlabeled iframes +- Autoplay audio +- Low contrast text +- Tiny touch targets +- Layouts that break at 200% zoom +- Keyboard traps +- Focus lost after DOM updates +- Infinite scroll without alternatives +- Toasts that disappear too quickly +- Captcha without accessible alternative + +## Common Clean Code Principles For Accessibility + +Accessibility code should be simple, explicit, and maintainable. + +Principles: + +- Prefer native platform behavior. +- Keep semantics close to the rendered element. +- Do not hide accessibility behavior in unrelated abstractions. +- Use component APIs that require labels. +- Keep ARIA state derived from the same source of truth as visual state. +- Avoid duplicated state where possible. +- Test behavior through roles and names. +- Do not over-abstract one-off accessibility logic. +- Encapsulate complex widget behavior in tested primitives. +- Document keyboard behavior for custom widgets. +- Treat accessibility bugs as functional bugs. +- Avoid CSS/JS tricks that create hidden focus or reading-order issues. +- Make inaccessible states hard to represent. +- Keep error message logic close to validation logic. +- Avoid “magic” aria props passed blindly through components. +- Prefer explicit accessible names over implicit guesses in reusable components. +- Use types/tests/lints to enforce required accessibility props. +- Keep focus management localized and predictable. +- Clean up timers, observers, and listeners that affect announcements or focus. +- Do not create global keyboard handlers without strong reason. + +## Accessible Component Checklist + +For any component, ask: + +- What semantic element should this be? +- What is its accessible name? +- Is it keyboard reachable? +- Is it keyboard operable? +- Is focus visible? +- What role, state, and value are exposed? +- Does it work with screen readers? +- Does it work at 200% zoom? +- Does it work in forced colors? +- Does it respect reduced motion? +- Does it support touch? +- Does it preserve logical reading order? +- Are errors/statuses announced? +- Is any information conveyed only by color, shape, position, sound, or motion? +- Does it remain usable when text is longer? +- Does it remain usable with translated content? +- Does it degrade gracefully without JavaScript where reasonable? + +## Accessible Page Checklist + +For each page/view: + +- Unique document title +- One main landmark +- Logical headings +- Skip link where useful +- Meaningful navigation labels +- Current page indicated +- All controls labeled +- All images have appropriate alternatives +- Forms have labels, hints, and errors +- Keyboard order is logical +- Focus is visible +- No keyboard traps +- Modals manage focus +- Dynamic updates are announced when needed +- Color contrast passes +- Text resizes/reflows +- Motion is controllable +- Media has captions/transcripts +- Touch targets are usable +- Works with screen reader smoke test + +## Accessibility Bugs Are Often State Bugs + +Many accessibility failures happen because state is inconsistent: + +- Visual menu open, `aria-expanded=false` +- Panel hidden visually but still focusable +- Button disabled visually but still activatable +- Modal shown but background still focusable +- Error visible but not associated with input +- Toast visible but not announced +- Route changed but focus stayed on old nav item +- Selected item styled but not programmatically selected +- Loading spinner shown without status text + +The fix is usually to make state explicit and synchronized. + +## Default Implementation Bias + +My default implementation bias would be: + +- Use real HTML elements. +- Use browser-native behavior. +- Add ARIA only when necessary. +- Keep focus behavior boring and predictable. +- Make labels visible. +- Make errors specific. +- Make dynamic updates restrained. +- Respect user preferences. +- Test with keyboard before considering a component done. +- Use automated checks as a floor, not a ceiling. +- Treat accessibility as part of the component contract. diff --git a/.agents/skills/project-discipline-guides/mirrors/accessibility/gemini_mirror.md b/.agents/skills/project-discipline-guides/mirrors/accessibility/gemini_mirror.md new file mode 100644 index 000000000..9323d427c --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/accessibility/gemini_mirror.md @@ -0,0 +1,99 @@ +# Comprehensive Web Accessibility (a11y) Common Knowledge Mirror + +This guide outlines the baseline accessibility knowledge, standard APIs, and modern best practices that I (the AI) natively understand and apply. This serves as a "Redundancy Mirror" to identify common knowledge that can be pruned from project-specific guidance. + +## 1. Semantic HTML & Document Structure +The foundation of accessibility is the correct use of HTML elements to convey meaning and structure to assistive technologies (AT). + +* **Landmark Elements:** Always use landmarks instead of generic `<div>`s for page structure: + * `<header>`: Page or section header. + * `<nav>`: Navigation blocks. + * `<main>`: The primary content of the document (only one per page). + * `<section>`: Generic section of a document (should usually have a heading). + * `<article>`: Self-contained composition (blog post, card, comment). + * `<aside>`: Indirectly related content (sidebar, callout). + * `<footer>`: Page or section footer. +* **Heading Hierarchy:** + * Maintain a logical order (`<h1>` through `<h6>`). + * Do not skip levels (e.g., `<h1>` followed by `<h3>`). + * Only one `<h1>` per page is recommended for clarity. +* **Lists:** Use `<ul>`, `<ol>`, and `<li>` for grouped items so screen readers announce the item count. +* **Buttons vs. Links:** + * `<a>`: Use for navigation to a different URL or anchor. + * `<button>`: Use for actions (submitting forms, opening dialogs, toggling menus). +* **Tables:** + * Use `<th>` for headers with `scope="col"` or `scope="row"`. + * Use `<caption>` to describe the table's purpose. + +## 2. WAI-ARIA (Accessible Rich Internet Applications) +ARIA should only be used when native HTML cannot provide the necessary semantics. + +* **The First Rule of ARIA:** If you can use a native HTML element or attribute with the semantics and behavior you require already built-in, instead of re-purposing an element and adding an ARIA role, state, or property to make it accessible, then do so. +* **Essential Attributes:** + * `aria-label`: Provides a string as the accessible name (overrides inner text). + * `aria-labelledby`: References another element's ID to provide the accessible name. + * `aria-describedby`: References another element's ID to provide an extended description. + * `aria-hidden="true"`: Removes an element and its children from the accessibility tree. + * `aria-live`: Notifies AT of dynamic content changes (`polite`, `assertive`). +* **States and Properties:** + * `aria-expanded`: Indicates if a menu or accordion is open/closed. + * `aria-pressed`: Indicates the state of a toggle button. + * `aria-checked`: Indicates the state of checkboxes or radio buttons. + * `aria-invalid`: Indicates that a form field has an error. + * `aria-required`: Indicates that a field must be filled. + * `aria-modal="true"`: Used inside a dialog to signal that content outside is inert. +* **Roles:** Use `role="alert"`, `role="status"`, `role="dialog"`, `role="tablist"`, `role="tab"`, `role="tabpanel"`, etc., to define widget structures. + +## 3. Focus Management & Keyboard Navigation +Every interactive element must be reachable and operable via keyboard. + +* **Focusability:** + * Native interactive elements (`<button>`, `<a>`, `<input>`, etc.) are focusable by default. + * `tabindex="0"`: Adds a non-interactive element (like a custom div component) to the natural tab order. + * `tabindex="-1"`: Makes an element programmatically focusable but removes it from the tab order. + * **Avoid** `tabindex > 0`. +* **Focus Styles:** + * Never remove focus outlines (`outline: none`) without providing a highly visible alternative. + * Use the `:focus-visible` pseudo-class to show focus indicators only when the user is navigating via keyboard. +* **Skip Links:** Provide a "Skip to Main Content" link as the first focusable element on the page. +* **Focus Trapping:** When a modal is open, focus must be contained within the modal and restored to the triggering element when closed. +* **The `inert` Attribute:** (Modern) Use the `inert` attribute on background content when a modal is active to prevent keyboard and screen reader access to "hidden" content. + +## 4. Forms & User Input +* **Labels:** Every form input must have a `<label>` associated via the `for` attribute (matching the input's `id`). +* **Grouping:** Use `<fieldset>` and `<legend>` to group related controls (like a set of radio buttons). +* **Error Handling:** + * Associate error messages with inputs using `aria-describedby`. + * Provide clear, text-based error messages (don't rely on color alone). + * Use `aria-live` to announce validation errors that appear dynamically. +* **Autocomplete:** Use the `autocomplete` attribute (e.g., `email`, `tel`, `address-line1`) to help users with cognitive disabilities and speed up form filling. + +## 5. Media & Visuals +* **Alt Text:** + * Informative images: Use `alt` to describe the content/purpose. + * Decorative images: Use `alt=""` to hide them from screen readers. + * Functional images (e.g., an icon inside a button): The `alt` text should describe the action (e.g., "Search", not "Magnifying glass"). +* **Color Contrast:** + * WCAG AA: 4.5:1 for normal text, 3:1 for large text/UI components. + * WCAG AAA: 7:1 for normal text, 4.5:1 for large text. +* **Don't Rely on Color Alone:** Use icons, text, or patterns to convey information (e.g., error states, active links). +* **Reduced Motion:** Respect user preferences via `@media (prefers-reduced-motion: reduce)`. Disable or slow down animations. + +## 6. Modern APIs & Cutting-Edge Features +* **`<dialog>` Element:** Use the native `<dialog>` element for modals. It handles focus trapping and the `Escape` key automatically when opened with `showModal()`. +* **Popover API:** (Modern) Use the `popover` attribute for non-modal overlays (tooltips, menus). It provides "light dismiss" behavior and top-layer management natively. +* **`aria-haspopup`:** Indicate that an element triggers a popup (menu, listbox, tree, grid, or dialog). +* **CSS `content-visibility: auto`:** Be aware that this can affect how screen readers find content on the page (items off-screen may be "missing" from the accessibility tree until rendered). + +## 7. JavaScript Interaction Patterns +* **Keyboard Events:** Listen for `Enter` and `Space` for custom controls. Ensure `Escape` closes overlays. +* **Dynamic Content:** When content updates without a page reload, use `aria-live` or move focus to the new content/container. +* **State Management:** Always sync the visual state with the ARIA state (e.g., changing `aria-expanded="true"` when a menu opens). + +## 8. Clean Code & Testing Principles +* **Automated Testing:** Use tools like `axe-core`, `Lighthouse`, or `eslint-plugin-jsx-a11y` as a baseline. +* **Manual Testing:** + * Navigate using only the `Tab`, `Shift+Tab`, `Enter`, `Space`, and `Arrow` keys. + * Test with a screen reader (VoiceOver, NVDA, or JAWS). + * Inspect the "Accessibility Tree" in Browser DevTools. +* **Progressive Enhancement:** Ensure the core functionality works with basic HTML before adding complex ARIA or JS-driven behaviors. diff --git a/.agents/skills/project-discipline-guides/mirrors/accessibility/mirror.md b/.agents/skills/project-discipline-guides/mirrors/accessibility/mirror.md new file mode 100644 index 000000000..d7d86a01f --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/accessibility/mirror.md @@ -0,0 +1,92 @@ +# Lowest Common Denominator (LCD) Accessibility Mirror + +This document represents the intersection of accessibility best practices, APIs, and guidelines consistently supported across the Gemini, Claude, and Codex knowledge mirrors. It serves as the baseline for "Common Knowledge" in web accessibility development. + +## 1. Semantic HTML & Document Structure + +* **Landmark Elements**: Use semantic landmarks to define page structure instead of generic containers: + * `<header>`: Page or section header. + * `<nav>`: Navigation blocks. + * `<main>`: Primary content (one per page). + * `<section>`: Document section. + * `<article>`: Self-contained composition. + * `<aside>`: Complementary or related content. + * `<footer>`: Page or section footer. +* **Heading Hierarchy**: + * Maintain a logical order (`<h1>` through `<h6>`). + * Do not skip heading levels (e.g., `<h1>` followed by `<h3>`). + * Use one `<h1>` per page to define the primary topic. +* **Lists**: Use `<ul>`, `<ol>`, and `<li>` for grouping items to ensure screen readers announce item counts. +* **Buttons vs. Links**: + * `<a>`: Use for navigation to a different URL or anchor. + * `<button>`: Use for actions (e.g., toggling menus, submitting forms, opening dialogs). +* **Tables**: + * Use `<th>` for headers with the `scope` attribute (`col` or `row`). + * Use `<caption>` to describe the table’s purpose. + +## 2. Forms & User Input + +* **Labels**: Every form input must have a `<label>` associated via the `for` attribute matching the input's `id`. +* **Grouping**: Use `<fieldset>` and `<legend>` to group related controls, such as radio button sets. +* **Input Types**: Use specific HTML5 input types (e.g., `type="email"`, `type="tel"`, `type="url"`) for better keyboard support and validation. +* **Autocomplete**: Use the `autocomplete` attribute to assist users with cognitive disabilities and speed up form completion. +* **Error Handling**: + * Associate error messages and help text with inputs using `aria-describedby`. + * Do not rely on color alone to indicate error states. + * Use `aria-live` to announce validation errors that appear dynamically. + +## 3. WAI-ARIA (Accessible Rich Internet Applications) + +* **The First Rule of ARIA**: Prioritize native HTML elements and attributes over ARIA roles and states whenever possible. +* **Essential Attributes**: + * `aria-label`: Provides a string as the accessible name (overrides inner text). + * `aria-labelledby`: References other element IDs to provide the accessible name. + * `aria-describedby`: References other element IDs to provide supplemental descriptions or error messages. + * `aria-hidden="true"`: Removes an element and its children from the accessibility tree. + * `aria-live`: Notifies assistive technology of dynamic content changes (`polite` or `assertive`). +* **States and Properties**: + * `aria-expanded`: Indicates if a widget (like a menu or accordion) is open or closed. + * `aria-pressed`: Indicates the state of a toggle button. + * `aria-checked`: Indicates the state of checkboxes or radio buttons. + * `aria-invalid`: Signals that a form field has an error. + * `aria-required`: Signals that a field must be filled. + * `aria-modal="true"`: Used within a dialog to signal that content outside the modal is inert. +* **Common Roles**: Use `role="alert"`, `role="status"`, `role="dialog"`, `role="tablist"`, `role="tab"`, and `role="tabpanel"` for custom widget structures. + +## 4. Keyboard Navigation & Focus Management + +* **Focusability**: + * `tabindex="0"`: Adds a non-interactive element to the natural tab order. + * `tabindex="-1"`: Makes an element programmatically focusable but removes it from the natural tab order. + * **Avoid** positive `tabindex` values (`tabindex > 0`). +* **Focus Indicators**: + * Never remove focus outlines (`outline: none`) without providing a visible alternative. + * Use the `:focus-visible` pseudo-class to show focus indicators only during keyboard navigation. +* **Modals & Overlays**: + * When a modal is open, focus must be trapped within the modal container. + * Focus must be restored to the triggering element when the modal is closed. + * `Escape` key should close modal overlays. +* **Skip Links**: Provide a "Skip to Main Content" link as the first focusable element on the page. + +## 5. Media & Visual Design + +* **Alt Text**: + * Informative images: Use `alt` to describe the content or purpose. + * Decorative images: Use `alt=""` (empty string) to hide them from screen readers. +* **Color Contrast**: Maintain WCAG AA standards (4.5:1 for normal text, 3:1 for large text/UI components). +* **Non-Color Cues**: Ensure information is not conveyed by color alone; use text, icons, or patterns as well. +* **Reduced Motion**: Respect user preferences via the `(prefers-reduced-motion: reduce)` media query to disable or slow down animations. + +## 6. Modern Web APIs + +* **`<dialog>` Element**: Use for modals to handle focus trapping and the `Escape` key automatically via `showModal()`. +* **Popover API**: Use the `popover` attribute for non-modal overlays (e.g., menus, tooltips) to provide native "light dismiss" behavior. +* **`inert` Attribute**: Use on background content when a modal is active to prevent keyboard and screen reader access to hidden content. + +## 7. Testing Principles + +* **Automated Testing**: Use tools like `axe-core` or `Lighthouse` as an initial baseline for catching common issues. +* **Manual Testing**: + * Verify navigation using only the keyboard (`Tab`, `Shift+Tab`, `Enter`, `Space`, `Escape`). + * Test with a screen reader (e.g., VoiceOver, NVDA) to ensure correct announcement of roles and states. + * Inspect the "Accessibility Tree" in browser developer tools. diff --git a/.agents/skills/project-discipline-guides/mirrors/css/claude_mirror.md b/.agents/skills/project-discipline-guides/mirrors/css/claude_mirror.md new file mode 100644 index 000000000..373045801 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/css/claude_mirror.md @@ -0,0 +1,912 @@ +# CSS Best Practices, Syntax, and APIs: Comprehensive Guide + +## 1. Selectors + +### Basic Selectors +- **Type**: `div`, `p`, `h1` +- **Class**: `.btn`, `.card` +- **ID**: `#header` (avoid for styling; use for fragments/JS hooks) +- **Universal**: `*` (use sparingly; performance impact) +- **Attribute**: `[type="text"]`, `[href^="https"]`, `[class*="btn-"]`, `[lang|="en"]`, `[data-state="open"]` + +### Combinators +- **Descendant**: `nav a` +- **Child**: `ul > li` +- **Adjacent sibling**: `h2 + p` +- **General sibling**: `h2 ~ p` + +### Pseudo-classes +- **State**: `:hover`, `:focus`, `:focus-visible`, `:focus-within`, `:active`, `:visited`, `:target` +- **Form**: `:checked`, `:disabled`, `:enabled`, `:required`, `:optional`, `:valid`, `:invalid`, `:in-range`, `:out-of-range`, `:placeholder-shown`, `:read-only`, `:read-write`, `:default`, `:indeterminate`, `:user-valid`, `:user-invalid` +- **Structural**: `:root`, `:empty`, `:first-child`, `:last-child`, `:only-child`, `:nth-child(n)`, `:nth-of-type(n)`, `:first-of-type`, `:last-of-type` +- **Logical**: `:is(h1, h2, h3)`, `:where(.a, .b)` (zero specificity), `:not(.foo)`, `:has(> img)` (parent selector) +- **Linguistic**: `:lang(en)`, `:dir(rtl)` +- **Other**: `:default`, `:scope`, `:any-link`, `:fullscreen`, `:modal`, `:popover-open` + +### Pseudo-elements +- `::before`, `::after` (require `content` property) +- `::first-letter`, `::first-line` +- `::selection`, `::placeholder` +- `::marker` (list bullets) +- `::backdrop` (dialog/fullscreen) +- `::file-selector-button` +- `::details-content` +- `::view-transition-*` + +### `:has()` Examples +```css +/* Card with image */ +.card:has(img) { padding-top: 0; } + +/* Form with invalid field */ +form:has(:invalid) button[type="submit"] { opacity: 0.5; } + +/* Layout adapts to children */ +.grid:has(> :nth-child(4)) { grid-template-columns: repeat(2, 1fr); } +``` + +### Specificity Best Practices +- Keep specificity low and flat +- Avoid IDs in selectors +- Avoid `!important` (use only for utility overrides if necessary) +- Use `:where()` for zero-specificity grouping +- Avoid deeply nested selectors + +--- + +## 2. The Cascade, Inheritance & Layers + +### Cascade Layers +```css +@layer reset, base, components, utilities; + +@layer reset { + * { margin: 0; } +} + +@layer components { + .btn { padding: 0.5rem 1rem; } +} +``` +- Layers control cascade independently of specificity +- Later layers win over earlier layers +- Unlayered styles win over layered styles + +### Inheritance +- Inherited: `color`, `font-*`, `line-height`, `text-*`, `visibility`, `cursor` +- Not inherited: `margin`, `padding`, `border`, `background`, `width`, `height` +- Force with `inherit`, `initial`, `unset`, `revert`, `revert-layer` + +### `all` Property +```css +.reset-component { all: unset; } +``` + +--- + +## 3. Custom Properties (CSS Variables) + +```css +:root { + --color-primary: oklch(60% 0.15 250); + --space-1: 0.25rem; + --space-2: 0.5rem; +} + +.btn { + background: var(--color-primary); + padding: var(--space-2) var(--space-4, 1rem); /* fallback */ +} +``` + +### Typed Custom Properties (`@property`) +```css +@property --gradient-angle { + syntax: "<angle>"; + inherits: false; + initial-value: 0deg; +} + +@keyframes spin-gradient { + to { --gradient-angle: 360deg; } +} +``` + +### Best Practices +- Define design tokens at `:root` +- Scope component variables to component selectors +- Use `--` prefix conventions: `--color-*`, `--space-*`, `--font-*`, `--radius-*` +- Custom properties cascade and inherit (unlike Sass variables) + +--- + +## 4. Layout + +### Flexbox +```css +.flex { + display: flex; + flex-direction: row | column; + flex-wrap: wrap; + gap: 1rem; + justify-content: flex-start | center | space-between | space-around | space-evenly; + align-items: stretch | center | flex-start | flex-end | baseline; + align-content: ...; +} + +.item { + flex: 1 1 auto; /* grow shrink basis */ + flex: 1; /* shorthand */ + align-self: center; + order: 2; +} +``` + +### Grid +```css +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + grid-template-rows: auto 1fr auto; + grid-template-areas: + "header header" + "sidebar main" + "footer footer"; + gap: 1rem; +} + +.item { + grid-column: 1 / -1; + grid-row: span 2; + grid-area: main; +} + +/* Subgrid */ +.child { + display: grid; + grid-template-columns: subgrid; +} +``` + +### Modern Layout Patterns +```css +/* Holy Grail / Auto-fit cards */ +.cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); + gap: 1rem; +} + +/* Stack with consistent gap */ +.stack > * + * { margin-block-start: 1rem; } + +/* Center anything */ +.center { display: grid; place-items: center; } +``` + +### Container Queries +```css +.card-container { + container-type: inline-size; + container-name: card; +} + +@container card (min-width: 400px) { + .card { display: grid; grid-template-columns: 1fr 2fr; } +} + +/* Container query units */ +.title { font-size: clamp(1rem, 5cqi, 2rem); } +``` + +### Aspect Ratio +```css +.video { aspect-ratio: 16 / 9; } +.square { aspect-ratio: 1; } +``` + +--- + +## 5. Box Model + +```css +*, *::before, *::after { box-sizing: border-box; } + +.box { + inline-size: 100%; /* logical: width */ + block-size: auto; /* logical: height */ + margin-inline: auto; + padding-block: 1rem; + border: 1px solid; + border-radius: 0.5rem; +} +``` + +### Logical Properties +Prefer logical over physical for i18n: +- `margin-inline-start` over `margin-left` +- `padding-block` over `padding-top`/`padding-bottom` +- `inset-inline` over `left`/`right` +- `border-inline-end` over `border-right` + +--- + +## 6. Units + +### Absolute +- `px` (most common for screens) +- `pt`, `pc`, `in`, `cm`, `mm` (rare on web; print) + +### Relative +- `em` (relative to element font-size) +- `rem` (relative to root font-size) — preferred for sizing +- `%` (relative to parent) +- `ch` (width of "0") +- `ex`, `cap`, `ic`, `lh`, `rlh` + +### Viewport +- `vw`, `vh`, `vmin`, `vmax` +- `svw/svh` (small), `lvw/lvh` (large), `dvw/dvh` (dynamic) — handles mobile browser chrome + +### Container +- `cqw`, `cqh`, `cqi`, `cqb`, `cqmin`, `cqmax` + +### Best Practices +- Use `rem` for typography and spacing +- Use `px` for borders and small fixed details +- Use `%` and `fr` for fluid layouts +- Use `dvh`/`svh`/`lvh` over `vh` for mobile + +--- + +## 7. Typography + +```css +:root { + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + line-height: 1.5; + text-rendering: optimizeLegibility; +} + +body { + font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem); +} + +/* Fluid type scale */ +h1 { font-size: clamp(2rem, 1.5rem + 2.5vw, 3.5rem); } + +/* Variable fonts */ +@font-face { + font-family: "Inter"; + src: url("inter.woff2") format("woff2-variations"); + font-weight: 100 900; + font-display: swap; +} + +.heading { + font-variation-settings: "wght" 650, "slnt" -5; +} + +/* Modern features */ +p { + text-wrap: pretty; /* or balance for headings */ + hyphens: auto; + hanging-punctuation: first last; + font-feature-settings: "kern", "liga"; + font-variant-numeric: tabular-nums; +} + +h1, h2, h3 { text-wrap: balance; } +``` + +### `@font-face` Best Practices +- Use `woff2` format +- Always set `font-display: swap` (or `optional`) +- Preload critical fonts: `<link rel="preload" as="font" crossorigin>` +- Use `size-adjust`, `ascent-override` to reduce CLS + +--- + +## 8. Color + +### Modern Color Functions +```css +:root { + --primary: oklch(60% 0.15 250); + --primary-light: oklch(from var(--primary) 80% c h); + --primary-alpha: oklch(60% 0.15 250 / 0.5); +} + +/* Wide-gamut */ +.vivid { color: color(display-p3 1 0 0.3); } + +/* Color mixing */ +.muted { background: color-mix(in oklch, var(--primary), white 30%); } + +/* Relative colors */ +.hover { background: hsl(from var(--primary) h s calc(l - 10%)); } +``` + +### Color Spaces +- `oklch()` — perceptually uniform, preferred for design systems +- `oklab()` — perceptual, Cartesian +- `lch()`, `lab()` — older perceptual +- `hsl()`, `hwb()` — intuitive but not perceptual +- `rgb()`, `rgba()` — legacy +- `color(display-p3 ...)` — wide gamut + +### Best Practices +- Use `oklch` for design tokens (predictable lightness) +- Slash syntax for alpha: `rgb(0 0 0 / 0.5)` +- `color-scheme: light dark;` for native UI theming +- Use `light-dark()` function for theme-aware colors + +```css +:root { color-scheme: light dark; } +.surface { background: light-dark(white, #111); } +``` + +--- + +## 9. Backgrounds, Borders, Effects + +```css +.box { + background: + linear-gradient(180deg, transparent, rgb(0 0 0 / 0.5)), + url("hero.jpg") center / cover no-repeat; + background-clip: text; /* gradient text */ + -webkit-background-clip: text; +} + +.card { + border: 1px solid oklch(80% 0 0); + border-radius: 0.5rem; + box-shadow: + 0 1px 2px oklch(0% 0 0 / 0.1), + 0 4px 12px oklch(0% 0 0 / 0.05); +} + +.glass { + backdrop-filter: blur(12px) saturate(180%); + background: oklch(100% 0 0 / 0.6); +} +``` + +### Modern Gradients +```css +.conic { background: conic-gradient(from 0deg, red, yellow, green, blue, red); } +.radial { background: radial-gradient(circle at top, white, black); } +.hue { background: linear-gradient(in oklch longer hue, red, blue); } +``` + +--- + +## 10. Transitions & Animations + +```css +.btn { + transition: background-color 200ms ease-out, + transform 150ms ease; +} + +.btn:hover { transform: translateY(-2px); } + +@keyframes fade-in { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +.entrance { + animation: fade-in 300ms ease-out both; +} + +/* Animate to/from auto with calc-size or display: none */ +.menu { + display: none; + transition: display 200ms allow-discrete, opacity 200ms; + opacity: 0; +} +.menu.open { + display: block; + opacity: 1; + @starting-style { opacity: 0; } +} + +/* Scroll-driven animations */ +@keyframes reveal { + from { opacity: 0; } + to { opacity: 1; } +} +.reveal { + animation: reveal linear; + animation-timeline: view(); + animation-range: entry 0% cover 30%; +} + +/* Respect motion preferences */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + transition-duration: 0.01ms !important; + } +} +``` + +### Best Practices +- Animate `transform` and `opacity` (compositor-only) for 60fps +- Avoid animating `width`, `height`, `top`, `left` (layout thrash) +- Use `will-change` sparingly; remove after animation +- Provide `prefers-reduced-motion` alternative + +--- + +## 11. Transforms + +```css +.box { + transform: translate(10px, 20px) rotate(45deg) scale(1.2); + transform-origin: top left; +} + +/* Individual transforms (composable) */ +.box { + translate: 10px 20px; + rotate: 45deg; + scale: 1.2; +} + +/* 3D */ +.scene { perspective: 1000px; } +.card { + transform-style: preserve-3d; + transform: rotateY(20deg); + backface-visibility: hidden; +} +``` + +--- + +## 12. Responsive Design + +### Mobile-First Media Queries +```css +.card { padding: 1rem; } + +@media (min-width: 48rem) { + .card { padding: 2rem; } +} + +/* Range syntax */ +@media (768px <= width < 1024px) { ... } + +/* Feature queries */ +@media (hover: hover) and (pointer: fine) { + .btn:hover { ... } +} + +@media (prefers-color-scheme: dark) { ... } +@media (prefers-reduced-motion: reduce) { ... } +@media (prefers-contrast: more) { ... } +@media (forced-colors: active) { ... } +@media (display-mode: standalone) { ... } +@media (orientation: portrait) { ... } +@media (resolution: 2dppx) { ... } +``` + +### Feature Queries +```css +@supports (display: grid) { + .layout { display: grid; } +} + +@supports not (selector(:has(*))) { + /* Fallback for browsers without :has */ +} +``` + +### Fluid Sizing with `clamp()` +```css +.container { + inline-size: clamp(20rem, 90vw, 75rem); + font-size: clamp(1rem, 0.9rem + 0.5vw, 1.25rem); + padding: clamp(1rem, 5vw, 3rem); +} +``` + +--- + +## 13. Functions + +### Math +- `calc()` — basic math +- `min()`, `max()`, `clamp()` +- `round()`, `mod()`, `rem()` +- `sin()`, `cos()`, `tan()`, `asin()`, `acos()`, `atan()`, `atan2()` +- `sqrt()`, `pow()`, `exp()`, `log()`, `hypot()`, `abs()`, `sign()` + +### Color +- `rgb()`, `hsl()`, `hwb()`, `lab()`, `lch()`, `oklab()`, `oklch()` +- `color()`, `color-mix()`, `light-dark()` + +### Other +- `var()`, `env()`, `attr()` +- `url()`, `image-set()` +- `linear-gradient()`, `radial-gradient()`, `conic-gradient()` + +--- + +## 14. Modern CSS Features + +### Nesting (Native) +```css +.card { + padding: 1rem; + + & .title { + font-size: 1.5rem; + } + + &:hover { + background: oklch(95% 0 0); + } + + @media (min-width: 48rem) { + padding: 2rem; + } +} +``` + +### Scope +```css +@scope (.card) to (.content) { + :scope { padding: 1rem; } + img { border-radius: 0.5rem; } +} +``` + +### Anchor Positioning +```css +.tooltip { + position: absolute; + position-anchor: --my-anchor; + top: anchor(bottom); + left: anchor(center); +} +``` + +### View Transitions +```css +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: 300ms; +} + +.card { view-transition-name: card; } +``` + +```js +document.startViewTransition(() => updateDOM()); +``` + +### Popover & Dialog +```css +[popover] { ... } +:popover-open { ... } +::backdrop { background: oklch(0% 0 0 / 0.5); } + +dialog[open] { ... } +dialog::backdrop { backdrop-filter: blur(4px); } +``` + +### Container Style Queries (limited support) +```css +@container style(--theme: dark) { ... } +``` + +--- + +## 15. Accessibility + +```css +/* Visible focus */ +:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; +} + +/* Don't remove outlines without replacement */ +:focus { outline: none; } /* BAD without :focus-visible */ + +/* Screen reader only */ +.sr-only { + position: absolute; + width: 1px; height: 1px; + padding: 0; margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + white-space: nowrap; + border: 0; +} + +/* Tap targets >= 44x44px */ +button { min-block-size: 2.75rem; min-inline-size: 2.75rem; } + +/* Respect user preferences */ +@media (prefers-reduced-motion: reduce) { ... } +@media (prefers-reduced-transparency: reduce) { ... } +@media (prefers-contrast: more) { ... } +@media (forced-colors: active) { + .btn { border: 1px solid CanvasText; } +} +``` + +### Best Practices +- Maintain WCAG color contrast (4.5:1 body, 3:1 large text) +- Don't convey info via color alone +- Use semantic HTML; CSS shouldn't replace it +- Use `pointer-events: none` carefully (breaks keyboard accessibility independently) +- Use system colors (`Canvas`, `CanvasText`, `LinkText`, etc.) in `forced-colors` + +--- + +## 16. Performance + +### Critical CSS +- Inline critical above-the-fold CSS +- Defer non-critical with `media="print"` toggle or `rel="preload"` + +### Properties That Are Cheap +- `transform`, `opacity`, `filter` (compositor-only) + +### Properties That Are Expensive +- Layout: `width`, `height`, `padding`, `margin`, `top/left` +- Paint: `color`, `background`, `box-shadow` + +### Containment +```css +.card { + contain: layout paint style; + content-visibility: auto; + contain-intrinsic-size: auto 300px; +} +``` + +### Will-change +```css +.menu { will-change: transform; } /* Use temporarily, not permanently */ +``` + +### Other +- Use `loading="lazy"` on images (HTML, but pairs with CSS) +- Use modern image formats (`avif`, `webp`) via `image-set()` +- Minimize selector complexity +- Avoid `@import` in CSS (blocks parallel loading); use `<link>` + +--- + +## 17. Forms & Inputs + +```css +input, textarea, select, button { + font: inherit; + color: inherit; +} + +input:user-invalid { border-color: red; } +input:user-valid { border-color: green; } + +input::placeholder { color: oklch(60% 0 0); } + +/* Custom checkboxes via accent-color (cheap) */ +input[type="checkbox"] { accent-color: var(--primary); } + +/* Form validation */ +input:required:invalid { ... } + +/* Field sizing */ +textarea { field-sizing: content; } +``` + +--- + +## 18. Logical Property Reference + +| Physical | Logical | +|---|---| +| `width` | `inline-size` | +| `height` | `block-size` | +| `margin-top` | `margin-block-start` | +| `margin-bottom` | `margin-block-end` | +| `margin-left` | `margin-inline-start` | +| `margin-right` | `margin-inline-end` | +| `padding-top` | `padding-block-start` | +| `top`, `bottom` | `inset-block-start`, `inset-block-end` | +| `left`, `right` | `inset-inline-start`, `inset-inline-end` | +| `text-align: left` | `text-align: start` | +| `border-left` | `border-inline-start` | + +Shorthands: `margin-block`, `margin-inline`, `padding-block`, `padding-inline`, `inset-block`, `inset-inline`. + +--- + +## 19. Naming & Organization + +### BEM +```css +.card { } +.card__title { } +.card__image { } +.card--featured { } +``` + +### Other Methodologies +- **OOCSS**: Separate structure from skin +- **SMACSS**: Base, Layout, Module, State, Theme +- **ITCSS**: Settings, Tools, Generic, Elements, Objects, Components, Utilities +- **Utility-first**: Tailwind-style atomic classes +- **CUBE CSS**: Composition, Utility, Block, Exception + +### File Organization +``` +styles/ + tokens/ + reset.css + base/ + layouts/ + components/ + utilities/ + themes/ + main.css +``` + +### Best Practices +- Use consistent naming convention throughout project +- Prefer classes over IDs and tags for styling +- Use kebab-case for class names +- Keep selectors shallow +- Group related properties; use a consistent order (e.g., positioning → box model → typography → visual) + +--- + +## 20. Reset / Normalize + +```css +*, *::before, *::after { box-sizing: border-box; } + +* { margin: 0; } + +html { -webkit-text-size-adjust: 100%; tab-size: 4; } + +body { + min-block-size: 100dvh; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +img, picture, video, canvas, svg { + display: block; + max-inline-size: 100%; +} + +input, button, textarea, select { font: inherit; } + +p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; } + +#root, #__next { isolation: isolate; } +``` + +--- + +## 21. At-rules Reference + +- `@import` (avoid; use bundler) +- `@media` +- `@supports` +- `@container` +- `@layer` +- `@scope` +- `@keyframes` +- `@font-face` +- `@font-feature-values` +- `@page` (print) +- `@property` +- `@starting-style` +- `@view-transition` +- `@charset` +- `@namespace` + +--- + +## 22. Common Anti-Patterns to Avoid + +- Using `!important` to win specificity battles (fix root cause) +- Magic numbers (`margin-top: 37px`) +- Deeply nested selectors (`.a .b .c .d span`) +- Styling by ID (`#header`) +- Type selectors with class (`div.card`) +- Removing focus outlines without replacement +- Using `px` for everything (no scaling for user font preferences) +- Animating `width`/`height`/`top`/`left` instead of `transform` +- Using `float` for layout (use Flexbox/Grid) +- Vendor prefixes for non-experimental features (use Autoprefixer if needed) +- `* { transition: all }` (causes unintended animations) +- `position: absolute` without a positioned parent +- Hiding content with `display: none` when accessibility matters (vs. `aria-hidden`/`hidden` attribute) +- Inline styles (except for dynamic values) +- Magic background images (use semantic `<img>` for content) + +--- + +## 23. Print Styles + +```css +@media print { + @page { margin: 2cm; } + body { color: black; background: white; } + a::after { content: " (" attr(href) ")"; } + .no-print { display: none; } +} +``` + +--- + +## 24. Internationalization + +```css +html { writing-mode: horizontal-tb; } + +[dir="rtl"] .icon { transform: scaleX(-1); } + +/* Use logical properties throughout */ +.card { padding-inline-start: 1rem; } + +/* Vertical writing modes */ +.vertical { writing-mode: vertical-rl; text-orientation: mixed; } +``` + +--- + +## 25. CSS-in-JS / Build Considerations + +- Prefer CSS Modules, Vanilla Extract, or plain CSS over runtime CSS-in-JS for performance +- Use PostCSS / Lightning CSS / esbuild for processing +- Autoprefixer for vendor prefixes +- Minify in production +- Use source maps in development +- Tree-shake unused styles (PurgeCSS, Tailwind JIT) + +--- + +## 26. Quick Reference: Sensible Defaults + +```css +:root { + color-scheme: light dark; + font-family: system-ui, sans-serif; + line-height: 1.5; + --radius: 0.5rem; + --space: 1rem; +} + +*, *::before, *::after { box-sizing: border-box; } +* { margin: 0; } + +body { + min-block-size: 100dvh; + -webkit-font-smoothing: antialiased; +} + +img, svg, video { display: block; max-inline-size: 100%; } + +:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; } + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} +``` diff --git a/.agents/skills/project-discipline-guides/mirrors/css/codex_mirror.md b/.agents/skills/project-discipline-guides/mirrors/css/codex_mirror.md new file mode 100644 index 000000000..b5a8d4e33 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/css/codex_mirror.md @@ -0,0 +1,3844 @@ +Below is a “Redundancy Mirror” of CSS knowledge I would treat as broadly common, native working knowledge when building modern web interfaces. It intentionally includes fundamentals, modern syntax, maintainability conventions, layout patterns, progressive enhancement, and browser API usage that I would apply by default without needing project-specific guidance. + +**CSS Best Practices And Common Knowledge** + +**Core Mental Model** + +CSS is a declarative styling language built around the cascade, inheritance, specificity, source order, and computed values. + +A good CSS implementation usually optimizes for: + +- Predictable cascade behavior +- Low specificity +- Reusable design tokens +- Responsive layouts by default +- Accessibility-preserving visual styles +- Maintainable component boundaries +- Minimal layout shifts +- Progressive enhancement for newer features +- Clear separation between structure, behavior, and presentation +- Avoiding brittle selectors that depend on incidental DOM structure + +CSS should generally describe visual rules, layout relationships, interaction states, and design-system constraints. JavaScript should usually handle state and behavior, while CSS should handle state presentation when possible through selectors, media queries, container queries, pseudo-classes, animations, and transitions. + +**The Cascade** + +The cascade determines which declaration wins when multiple rules target the same element. + +Cascade order is affected by: + +- Origin: user-agent, user, author +- Importance: normal vs `!important` +- Cascade layers +- Specificity +- Scoping proximity, where relevant +- Source order + +Avoid relying on high specificity unless necessary. Prefer predictable ordering, layers, component boundaries, and custom properties. + +Avoid excessive use of `!important`. It is appropriate for narrow utility overrides, accessibility-enforced styles, user preference overrides, or integration escape hatches, but should not be the default way to win conflicts. + +**Specificity** + +Specificity roughly ranks selectors as: + +- Inline styles +- IDs +- Classes, attributes, and pseudo-classes +- Elements and pseudo-elements + +Examples: + +```css +button { + color: black; +} + +.button { + color: blue; +} + +#submit { + color: red; +} +``` + +Prefer class selectors for authored styles. Avoid styling with IDs because they create high specificity and reduce reusability. + +Avoid deep descendant selectors such as: + +```css +.page .sidebar .section .card .title { +} +``` + +Prefer a local class: + +```css +.card-title { +} +``` + +Use `:where()` to intentionally keep specificity low: + +```css +:where(article, section, aside) h2 { + margin-block-start: 0; +} +``` + +`:where()` always has zero specificity. + +Use `:is()` to group selectors while preserving the specificity of its most specific argument: + +```css +:is(h1, h2, h3) { + line-height: 1.1; +} +``` + +Use `:not()` for exclusion logic, but avoid making selectors hard to understand: + +```css +.button:not(:disabled) { + cursor: pointer; +} +``` + +Use `:has()` for parent/state-aware styling where supported: + +```css +.field:has(input:invalid) { + border-color: red; +} +``` + +Treat `:has()` as a powerful modern selector. It is useful for forms, cards, navigation state, layout adjustments, and progressive enhancement, but avoid overly broad expensive selectors such as `body:has(...)` unless necessary. + +**Cascade Layers** + +Use `@layer` to create predictable groups of CSS. + +Common layer order: + +```css +@layer reset, base, tokens, layout, components, utilities, overrides; +``` + +Example: + +```css +@layer reset { + *, + *::before, + *::after { + box-sizing: border-box; + } +} + +@layer base { + body { + margin: 0; + font-family: system-ui, sans-serif; + } +} + +@layer components { + .button { + display: inline-flex; + align-items: center; + gap: 0.5rem; + } +} + +@layer utilities { + .sr-only { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + } +} +``` + +Unlayered author styles outrank layered styles, so if using layers, be intentional about where third-party and app CSS are placed. + +Cascade layers are especially useful for: + +- Resets +- Design systems +- Utility classes +- Third-party CSS +- Framework integration +- Large applications with multiple style origins + +**Inheritance** + +Many text-related properties inherit by default: + +- `color` +- `font-family` +- `font-size` +- `font-weight` +- `line-height` +- `text-align` +- `visibility` + +Many box/layout properties do not inherit: + +- `margin` +- `padding` +- `border` +- `display` +- `width` +- `height` +- `background` + +Use inheritance deliberately for typography and design tokens. + +Useful global patterns: + +```css +body { + color: var(--color-text); + font-family: var(--font-body); + line-height: 1.5; +} + +button, +input, +textarea, +select { + font: inherit; +} +``` + +**Box Model** + +Use `box-sizing: border-box` globally: + +```css +*, +*::before, +*::after { + box-sizing: border-box; +} +``` + +This makes widths and heights include padding and border, which is usually easier to reason about. + +Understand: + +- Content box +- Padding +- Border +- Margin +- Scrollable overflow +- Containing blocks +- Formatting contexts + +Margins can collapse vertically in normal block flow. Padding, borders, flex/grid containers, overflow contexts, and flow-root containers can prevent margin collapse. + +Use logical properties instead of physical properties where possible: + +```css +.card { + padding-block: 1rem; + padding-inline: 1.25rem; + margin-block-end: 1rem; +} +``` + +Prefer: + +- `margin-inline` +- `margin-block` +- `padding-inline` +- `padding-block` +- `border-inline` +- `border-block` +- `inset-inline` +- `inset-block` + +Instead of always using: + +- `margin-left` +- `margin-right` +- `padding-top` +- `bottom` + +Logical properties support different writing modes and directions. + +**Modern Units** + +Common length units: + +```css +px +rem +em +% +vw +vh +vmin +vmax +ch +ex +lh +rlh +``` + +Use `rem` for type and most spacing that should scale with user preferences. + +Use `em` for values that should scale relative to the element’s font size. + +Use `ch` for readable text widths: + +```css +.article { + max-inline-size: 70ch; +} +``` + +Use modern viewport units for mobile-safe layouts: + +```css +.hero { + min-block-size: 100dvh; +} +``` + +Relevant viewport units: + +- `vh`, `vw`: classic viewport units +- `svh`, `svw`: small viewport +- `lvh`, `lvw`: large viewport +- `dvh`, `dvw`: dynamic viewport + +Prefer `dvh` for full-height app surfaces when browser UI may expand/collapse. + +Use `min()`, `max()`, and `clamp()` for responsive constraints: + +```css +.container { + inline-size: min(100% - 2rem, 72rem); + margin-inline: auto; +} + +h1 { + font-size: clamp(2rem, 5vw, 4rem); +} +``` + +However, avoid viewport-scaled text when predictable readability is more important. Fluid type should be constrained and tested. + +Use `calc()` for arithmetic: + +```css +.sidebar { + inline-size: calc(100% - var(--nav-width)); +} +``` + +Modern CSS supports math functions such as: + +```css +min() +max() +clamp() +calc() +round() +mod() +rem() +sin() +cos() +tan() +asin() +acos() +atan() +atan2() +pow() +sqrt() +hypot() +log() +exp() +abs() +sign() +``` + +Browser support varies for newer math functions, so use progressive enhancement for less-established ones. + +**Custom Properties** + +Use CSS custom properties for design tokens and runtime-themable values: + +```css +:root { + --color-bg: #ffffff; + --color-text: #111827; + --space-2: 0.5rem; + --radius-md: 0.5rem; +} +``` + +Use them in declarations: + +```css +.card { + background: var(--color-bg); + color: var(--color-text); + padding: var(--space-2); + border-radius: var(--radius-md); +} +``` + +Use fallbacks: + +```css +.button { + color: var(--button-color, currentColor); +} +``` + +Custom properties inherit by default. + +Use component-local variables to make variants easier: + +```css +.button { + --button-bg: black; + --button-color: white; + + background: var(--button-bg); + color: var(--button-color); +} + +.button[data-variant='danger'] { + --button-bg: crimson; +} +``` + +This is often cleaner than duplicating full rule blocks. + +Use `@property` to register typed custom properties when animating or constraining them: + +```css +@property --progress { + syntax: '<number>'; + inherits: false; + initial-value: 0; +} +``` + +This enables smoother interpolation for supported custom values. + +Use progressive enhancement for `@property` if older browser support matters. + +**Design Tokens** + +Common token categories: + +```css +--color-* +--font-* +--text-* +--space-* +--radius-* +--shadow-* +--border-* +--z-* +--duration-* +--ease-* +--container-* +``` + +Prefer semantic tokens over hard-coded values in components: + +```css +:root { + --color-surface: white; + --color-surface-muted: #f8fafc; + --color-text: #0f172a; + --color-text-muted: #64748b; + --color-accent: #2563eb; +} +``` + +Avoid using raw palette names everywhere: + +```css +/* Less maintainable */ +.alert { + color: var(--red-700); +} + +/* More semantic */ +.alert { + color: var(--color-danger-text); +} +``` + +A good system often has both: + +- Primitive tokens: `--blue-500`, `--gray-100` +- Semantic tokens: `--color-action-bg`, `--color-danger-text` + +**Color** + +Modern CSS supports many color formats: + +```css +#fff +#ffffff +rgb(255 255 255) +rgb(255 255 255 / 0.8) +hsl(220 80% 50%) +hsl(220 80% 50% / 0.8) +lab() +lch() +oklab() +oklch() +color() +color-mix() +``` + +Prefer modern space-separated syntax: + +```css +color: rgb(15 23 42 / 0.9); +``` + +Use `currentColor` for borders, icons, and decorations that should follow text color: + +```css +.icon { + color: var(--color-accent); + stroke: currentColor; +} +``` + +Use `transparent` carefully, especially with gradients, because transparent black can affect interpolation. Prefer explicit alpha colors where necessary. + +Use `color-mix()` for deriving related colors: + +```css +.button:hover { + background: color-mix(in oklch, var(--color-accent), black 10%); +} +``` + +Use `oklch()` or `oklab()` for perceptually consistent color adjustments when supported. + +Example: + +```css +:root { + --brand: oklch(55% 0.18 250); +} +``` + +Use `@supports` to progressively enhance modern color: + +```css +.button { + background: #2563eb; +} + +@supports (background: oklch(55% 0.18 250)) { + .button { + background: oklch(55% 0.18 250); + } +} +``` + +Ensure color contrast meets accessibility requirements. Text contrast should generally meet WCAG guidance: + +- Normal text: at least 4.5:1 +- Large text: at least 3:1 +- Non-text UI indicators: at least 3:1 where applicable + +Do not rely on color alone to convey state. Pair color with text, iconography, shape, position, or ARIA-accessible state. + +**Typography** + +Set a sensible body font stack: + +```css +body { + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + sans-serif; +} +``` + +Use `line-height` unitlessly: + +```css +body { + line-height: 1.5; +} +``` + +Unitless line-height scales better with inherited font sizes. + +Use readable line lengths: + +```css +.prose { + max-inline-size: 65ch; +} +``` + +Use `font-size-adjust` when fallback metrics matter. + +Use `font-feature-settings` sparingly. Prefer higher-level properties when available: + +```css +body { + font-kerning: normal; + font-variant-ligatures: common-ligatures; +} +``` + +Use `font-variation-settings` for variable fonts only when specific axes are needed: + +```css +.logo { + font-variation-settings: 'wght' 650; +} +``` + +Prefer standard properties like `font-weight`, `font-stretch`, and `font-style` where possible. + +Use `@font-face` for custom fonts: + +```css +@font-face { + font-family: 'Inter'; + src: url('/fonts/inter.woff2') format('woff2'); + font-weight: 100 900; + font-display: swap; +} +``` + +Use `font-display: swap` or `optional` to reduce invisible text. + +Prefer `woff2`. + +Avoid loading too many font weights/styles. Variable fonts can reduce requests but still need performance care. + +Use `text-wrap` where appropriate: + +```css +h1, +h2, +h3 { + text-wrap: balance; +} + +p { + text-wrap: pretty; +} +``` + +`text-wrap: balance` is useful for headings and short blocks. `text-wrap: pretty` can improve paragraph wrapping where supported. + +Use `overflow-wrap` for long content: + +```css +.content { + overflow-wrap: break-word; +} +``` + +For very defensive wrapping: + +```css +.content { + overflow-wrap: anywhere; +} +``` + +Use `hyphens: auto` where language is specified: + +```css +article { + hyphens: auto; +} +``` + +Ensure the document has a correct `lang` attribute for hyphenation and accessibility. + +**Resets And Base Styles** + +A modern reset often includes: + +```css +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + -webkit-text-size-adjust: 100%; +} + +body { + margin: 0; + min-block-size: 100vh; + font-family: system-ui, sans-serif; + line-height: 1.5; +} + +img, +picture, +svg, +canvas, +video { + display: block; + max-inline-size: 100%; +} + +button, +input, +textarea, +select { + font: inherit; +} + +button { + cursor: pointer; +} + +:disabled { + cursor: not-allowed; +} + +textarea { + resize: vertical; +} +``` + +Avoid removing all default focus styles globally. + +Bad: + +```css +*:focus { + outline: none; +} +``` + +Better: + +```css +:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} +``` + +Use `:focus-visible` to avoid noisy mouse focus while preserving keyboard focus. + +Use `prefers-reduced-motion` to reduce animations: + +```css +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + } +} +``` + +A less aggressive version may be better in app-specific contexts. + +**Layout Fundamentals** + +Prefer normal document flow where possible. Reach for absolute positioning only when an element should be removed from flow. + +Common layout mechanisms: + +- Block flow +- Inline flow +- Flexbox +- Grid +- Multi-column layout +- Positioned layout +- Float, mostly for legacy text wrapping +- Table layout, mostly for actual tables or special layout constraints + +Use `display: flow-root` to create a new block formatting context: + +```css +.card { + display: flow-root; +} +``` + +This can contain floats and prevent margin collapse. + +**Flexbox** + +Use flexbox for one-dimensional layout: rows or columns. + +```css +.toolbar { + display: flex; + align-items: center; + gap: 0.75rem; +} +``` + +Common properties: + +```css +display: flex; +flex-direction: row | column; +flex-wrap: wrap; +justify-content: flex-start | center | space-between; +align-items: stretch | center | baseline; +align-content: start | center | space-between; +gap: 1rem; +``` + +Child properties: + +```css +.item { + flex: 1 1 auto; + align-self: center; + order: 2; +} +``` + +Use `gap` instead of margins for spacing between flex children. + +Know that flex items have `min-width: auto` by default, which can cause overflow. Use: + +```css +.flex-child { + min-inline-size: 0; +} +``` + +This is common for truncation and nested layouts. + +For equal-width flexible children: + +```css +.item { + flex: 1 1 0; +} +``` + +For fixed-size controls in a flexible row: + +```css +.icon-button { + flex: 0 0 auto; +} +``` + +For wrapping layouts: + +```css +.cluster { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; +} +``` + +**Grid** + +Use grid for two-dimensional layout. + +```css +.dashboard { + display: grid; + grid-template-columns: 16rem 1fr; + gap: 1rem; +} +``` + +Use `fr` units for distributing available space: + +```css +.layout { + grid-template-columns: 1fr 2fr; +} +``` + +Use `minmax()` to prevent overflow: + +```css +.grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} +``` + +Use responsive auto-fit patterns: + +```css +.cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); + gap: 1rem; +} +``` + +Use `auto-fill` when you want empty tracks preserved; use `auto-fit` when you want tracks to collapse and items to stretch. + +Use named grid areas for page layouts where readability helps: + +```css +.page { + display: grid; + grid-template: + 'header header' auto + 'nav main' 1fr + / 16rem 1fr; +} + +.header { + grid-area: header; +} +``` + +Use subgrid where supported to align nested content to parent grid tracks: + +```css +.card-list { + display: grid; + grid-template-columns: repeat(3, 1fr); +} + +.card { + display: grid; + grid-template-rows: subgrid; +} +``` + +Progressively enhance `subgrid` if supporting older browsers. + +Use alignment properties: + +```css +place-items: center; +place-content: center; +justify-items: start; +align-items: center; +``` + +`place-*` shorthands combine block and inline axis alignment. + +**Container Queries** + +Use container queries when components should respond to their own available space, not the viewport. + +Define a query container: + +```css +.card-shell { + container-type: inline-size; +} +``` + +Query it: + +```css +@container (min-width: 36rem) { + .card { + display: grid; + grid-template-columns: 12rem 1fr; + } +} +``` + +Use named containers when needed: + +```css +.sidebar { + container: sidebar / inline-size; +} + +@container sidebar (min-width: 24rem) { + .filter-panel { + display: grid; + } +} +``` + +Use container query units: + +```css +.card-title { + font-size: clamp(1rem, 5cqi, 1.5rem); +} +``` + +Container query units include: + +- `cqw` +- `cqh` +- `cqi` +- `cqb` +- `cqmin` +- `cqmax` + +Use container queries for: + +- Cards +- Sidebars +- Toolbars +- Reusable modules +- Embeddable widgets +- Responsive components inside variable app layouts + +Prefer media queries for global layout shifts and environment preferences. Prefer container queries for component-local responsiveness. + +**Media Queries** + +Use media queries for viewport, device, and user preference conditions. + +```css +@media (min-width: 48rem) { + .layout { + display: grid; + grid-template-columns: 16rem 1fr; + } +} +``` + +Use range syntax: + +```css +@media (width >= 48rem) { +} +``` + +Common media features: + +```css +width +height +orientation +aspect-ratio +resolution +hover +any-hover +pointer +any-pointer +prefers-color-scheme +prefers-reduced-motion +prefers-contrast +forced-colors +prefers-reduced-transparency +prefers-reduced-data +update +scripting +display-mode +``` + +Use pointer/hover queries for interaction design: + +```css +@media (hover: hover) and (pointer: fine) { + .button:hover { + background: var(--button-hover); + } +} +``` + +Use dark mode: + +```css +@media (prefers-color-scheme: dark) { + :root { + --color-bg: #0f172a; + --color-text: #f8fafc; + } +} +``` + +Use high contrast/forced colors support: + +```css +@media (forced-colors: active) { + .button { + border: 1px solid ButtonText; + } +} +``` + +Do not assume hover exists. Touch devices may not support hover reliably. + +**Responsive Design** + +Prefer mobile-first or content-first CSS: + +```css +.card { + display: grid; + gap: 1rem; +} + +@media (width >= 48rem) { + .card { + grid-template-columns: 16rem 1fr; + } +} +``` + +Use intrinsic layout patterns instead of fixed breakpoints where possible: + +```css +.grid { + grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr)); +} +``` + +Use fluid constraints: + +```css +.wrapper { + inline-size: min(100% - 2rem, 72rem); + margin-inline: auto; +} +``` + +Avoid fixed widths that break small screens: + +```css +/* Avoid */ +.modal { + width: 600px; +} + +/* Better */ +.modal { + inline-size: min(100% - 2rem, 37.5rem); +} +``` + +Prefer `max-inline-size` and `min()` for defensive sizing. + +Ensure text, buttons, forms, tables, and media do not overflow small viewports. + +Use horizontal scrolling only when appropriate, such as data tables: + +```css +.table-wrap { + overflow-x: auto; +} +``` + +**Positioning** + +Position values: + +```css +static +relative +absolute +fixed +sticky +``` + +Use `position: relative` to establish a containing block for absolute children. + +```css +.card { + position: relative; +} + +.badge { + position: absolute; + inset-block-start: 0.5rem; + inset-inline-end: 0.5rem; +} +``` + +Use logical inset properties: + +```css +.toast { + position: fixed; + inset-block-end: 1rem; + inset-inline-end: 1rem; +} +``` + +Use `position: sticky` for sticky headers/sidebars: + +```css +.section-nav { + position: sticky; + inset-block-start: 1rem; +} +``` + +Sticky requires room to stick and can be affected by overflow ancestors. + +Use z-index deliberately. Create a z-index scale: + +```css +:root { + --z-dropdown: 1000; + --z-sticky: 1100; + --z-modal: 1200; + --z-toast: 1300; +} +``` + +Remember stacking contexts can be created by: + +- Positioned elements with z-index +- `opacity` less than 1 +- `transform` +- `filter` +- `perspective` +- `isolation: isolate` +- `contain` +- `will-change` +- `mix-blend-mode` +- Some `clip-path`, `mask`, and animation states + +Use `isolation: isolate` to contain stacking behavior: + +```css +.app-shell { + isolation: isolate; +} +``` + +**Overflow And Scrolling** + +Use overflow intentionally: + +```css +.panel { + overflow: auto; +} +``` + +Use axis-specific properties: + +```css +.panel { + overflow-x: auto; + overflow-y: hidden; +} +``` + +Use `overflow: clip` when content should be clipped without creating a scroll container: + +```css +.media { + overflow: clip; +} +``` + +Use `scrollbar-gutter` to avoid layout shift when scrollbars appear: + +```css +html { + scrollbar-gutter: stable; +} +``` + +Use scroll snapping for carousels or paged sections where appropriate: + +```css +.carousel { + display: flex; + overflow-x: auto; + scroll-snap-type: x mandatory; +} + +.slide { + flex: 0 0 100%; + scroll-snap-align: start; +} +``` + +Use `scroll-margin` for anchored headings under sticky headers: + +```css +[id] { + scroll-margin-block-start: 5rem; +} +``` + +Use `overscroll-behavior` to control scroll chaining: + +```css +.modal { + overscroll-behavior: contain; +} +``` + +Use `scroll-behavior: smooth` carefully and disable or avoid it for users who prefer reduced motion: + +```css +html { + scroll-behavior: smooth; +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +} +``` + +**Sizing** + +Prefer logical sizing: + +```css +.box { + inline-size: 100%; + max-inline-size: 64rem; + block-size: auto; +} +``` + +Use `min-block-size` rather than `height` for flexible vertical sections: + +```css +.hero { + min-block-size: 100dvh; +} +``` + +Use aspect ratio for media and fixed-format UI: + +```css +.thumbnail { + aspect-ratio: 16 / 9; + object-fit: cover; +} +``` + +Use intrinsic sizing keywords: + +```css +width: min-content; +width: max-content; +width: fit-content; +``` + +Use defensive grid/flex sizing: + +```css +grid-template-columns: minmax(0, 1fr); +min-inline-size: 0; +``` + +Avoid setting `height: 100vh` for mobile full-screen UIs unless dynamic viewport behavior has been considered. + +Prefer: + +```css +min-block-size: 100dvh; +``` + +**Images And Media** + +Make images responsive: + +```css +img { + max-inline-size: 100%; + block-size: auto; +} +``` + +Use `object-fit`: + +```css +.avatar { + inline-size: 3rem; + aspect-ratio: 1; + object-fit: cover; + border-radius: 50%; +} +``` + +Values: + +```css +fill +contain +cover +none +scale-down +``` + +Use `object-position` for crop alignment: + +```css +.hero-image { + object-fit: cover; + object-position: center top; +} +``` + +Use `aspect-ratio` to prevent layout shift. + +Use `background-image` for decorative images, not meaningful content. Use `<img>` or `<picture>` for content images. + +Use CSS gradients for lightweight decoration: + +```css +.banner { + background: + linear-gradient(rgb(0 0 0 / 0.3), rgb(0 0 0 / 0.3)), + url('/image.jpg') center / cover; +} +``` + +**Backgrounds And Borders** + +Useful background properties: + +```css +background-color +background-image +background-repeat +background-position +background-size +background-origin +background-clip +background-attachment +``` + +Use shorthand carefully: + +```css +.hero { + background: url('/hero.jpg') center / cover no-repeat; +} +``` + +Multiple backgrounds stack front-to-back: + +```css +.hero { + background: + linear-gradient(rgb(0 0 0 / 0.4), rgb(0 0 0 / 0.4)), + url('/hero.jpg') center / cover no-repeat; +} +``` + +Use border logical properties: + +```css +.card { + border-block-start: 1px solid var(--color-border); +} +``` + +Use `border-radius` consistently from tokens: + +```css +.card { + border-radius: var(--radius-md); +} +``` + +Use `outline` for focus rings because it does not affect layout. + +**Shadows And Elevation** + +Use shadows sparingly and consistently. + +```css +:root { + --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.08); + --shadow-md: 0 8px 24px rgb(0 0 0 / 0.12); +} +``` + +Avoid overly heavy shadows. Combine subtle border and shadow for surfaces: + +```css +.card { + border: 1px solid rgb(0 0 0 / 0.08); + box-shadow: var(--shadow-sm); +} +``` + +Use `filter: drop-shadow()` for irregular shapes and transparent images: + +```css +.logo { + filter: drop-shadow(0 2px 4px rgb(0 0 0 / 0.2)); +} +``` + +Use performance care with large blurred shadows and filters. + +**Forms** + +Preserve usability and accessibility. + +Use `font: inherit` on controls. + +```css +input, +button, +textarea, +select { + font: inherit; +} +``` + +Use visible focus states: + +```css +input:focus-visible, +select:focus-visible, +textarea:focus-visible, +button:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} +``` + +Style disabled states clearly: + +```css +:disabled { + opacity: 0.55; + cursor: not-allowed; +} +``` + +Use pseudo-classes: + +```css +input:required +input:optional +input:valid +input:invalid +input:user-valid +input:user-invalid +input:placeholder-shown +input:checked +input:indeterminate +input:disabled +input:enabled +input:read-only +input:read-write +``` + +Example: + +```css +.field:has(input:user-invalid) .error { + display: block; +} +``` + +Use `accent-color` for native checkbox/radio/range accents: + +```css +:root { + accent-color: var(--color-accent); +} +``` + +Use `caret-color` where useful: + +```css +input { + caret-color: var(--color-accent); +} +``` + +Use `color-scheme` to improve native form controls in dark mode: + +```css +:root { + color-scheme: light dark; +} +``` + +Or per theme: + +```css +[data-theme='dark'] { + color-scheme: dark; +} +``` + +Use `appearance: none` only when fully replacing native control styling and preserving accessibility. + +Avoid making custom select/checkbox/radio controls unless necessary. Native controls are usually more accessible and robust. + +Use `resize: vertical` for textareas unless a fixed behavior is required. + +```css +textarea { + resize: vertical; +} +``` + +**Buttons And Interactive Elements** + +Use semantic HTML first: + +- `<button>` for actions +- `<a>` for navigation +- Form controls for form inputs + +CSS should not compensate for incorrect semantics. + +Button baseline: + +```css +.button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + min-block-size: 2.5rem; + padding-inline: 1rem; + border: 1px solid transparent; + border-radius: var(--radius-sm); + font: inherit; + font-weight: 600; + text-decoration: none; + cursor: pointer; +} +``` + +Support states: + +```css +.button:hover { +} + +.button:active { +} + +.button:focus-visible { +} + +.button:disabled, +.button[aria-disabled='true'] { +} +``` + +Do not rely only on `:hover`. + +For icon buttons, ensure stable dimensions: + +```css +.icon-button { + display: inline-grid; + place-items: center; + inline-size: 2.5rem; + block-size: 2.5rem; +} +``` + +Ensure accessible names are provided in HTML via text, `aria-label`, or associated content. + +**Links** + +Links should be visually identifiable. + +```css +a { + color: var(--color-link); + text-decoration-thickness: from-font; + text-underline-offset: 0.15em; +} +``` + +Use hover/focus states: + +```css +a:hover { + text-decoration-style: solid; +} + +a:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} +``` + +Avoid removing underlines from body text links unless another clear affordance exists. + +**Tables** + +Use semantic tables for tabular data. + +```css +table { + inline-size: 100%; + border-collapse: collapse; +} + +th, +td { + padding: 0.75rem; + text-align: start; + border-block-end: 1px solid var(--color-border); +} +``` + +Use wrappers for overflow: + +```css +.table-wrap { + overflow-x: auto; +} +``` + +Use `caption-side` if styling captions: + +```css +caption { + caption-side: bottom; +} +``` + +Use sticky headers carefully: + +```css +thead th { + position: sticky; + inset-block-start: 0; + background: var(--color-surface); +} +``` + +**Lists** + +Use semantic lists for grouped items. + +Remove list styling only when the semantics remain useful but the visual bullets are unwanted: + +```css +.nav-list { + list-style: none; + margin: 0; + padding: 0; +} +``` + +Use `::marker` for marker styling: + +```css +li::marker { + color: var(--color-accent); +} +``` + +**Pseudo-Classes** + +Common pseudo-classes: + +```css +:hover +:active +:focus +:focus-visible +:focus-within +:visited +:target +:checked +:disabled +:enabled +:required +:optional +:valid +:invalid +:user-valid +:user-invalid +:placeholder-shown +:first-child +:last-child +:only-child +:nth-child() +:nth-last-child() +:first-of-type +:last-of-type +:empty +:not() +:is() +:where() +:has() +:root +:scope +:lang() +:dir() +:fullscreen +:popover-open +:modal +``` + +Use `:focus-within` for parent styling: + +```css +.search { + border: 1px solid var(--color-border); +} + +.search:focus-within { + border-color: var(--color-focus); +} +``` + +Use `:target` for anchor-linked sections: + +```css +:target { + scroll-margin-block-start: 5rem; +} +``` + +Use modern `nth-child` selector syntax: + +```css +.item:nth-child(odd) { +} + +.item:nth-child(3n + 1) { +} + +.item:nth-child(2 of .featured) { +} +``` + +Use `:empty` cautiously because whitespace text nodes affect it. + +**Pseudo-Elements** + +Common pseudo-elements: + +```css +::before +::after +::marker +::placeholder +::selection +::backdrop +::file-selector-button +::first-letter +::first-line +::cue +::part() +::slotted() +``` + +Use `::before` and `::after` for decorative generated content: + +```css +.badge::before { + content: ''; + inline-size: 0.5rem; + block-size: 0.5rem; + border-radius: 50%; + background: currentColor; +} +``` + +Do not put meaningful content only in CSS pseudo-elements because assistive technology support can vary and generated content is not a substitute for semantic HTML. + +Style selection: + +```css +::selection { + background: var(--color-selection-bg); + color: var(--color-selection-text); +} +``` + +Style placeholders with adequate contrast: + +```css +::placeholder { + color: var(--color-text-muted); + opacity: 1; +} +``` + +Use `::backdrop` for dialogs/popovers: + +```css +dialog::backdrop { + background: rgb(0 0 0 / 0.5); +} +``` + +Use `::file-selector-button` for file inputs: + +```css +input[type='file']::file-selector-button { + font: inherit; +} +``` + +**Nesting** + +Modern CSS supports native nesting. + +```css +.card { + padding: 1rem; + + & h2 { + margin-block-start: 0; + } + + &:hover { + border-color: var(--color-accent); + } + + @media (width >= 48rem) { + padding: 1.5rem; + } +} +``` + +Use nesting sparingly. Avoid deeply nested selectors because they increase coupling and specificity. + +Good: + +```css +.card { + & > header { + } + + & .card-title { + } + + &:has(img) { + } +} +``` + +Avoid: + +```css +.page { + & .sidebar { + & .nav { + & ul { + & li { + & a { + } + } + } + } + } +} +``` + +Nested CSS should improve locality, not recreate DOM trees. + +**Scoping** + +Use local component classes, cascade layers, CSS modules, shadow DOM, or naming conventions to keep styles scoped. + +Modern CSS includes `@scope`: + +```css +@scope (.card) { + h2 { + margin-block-start: 0; + } +} +``` + +Use progressive enhancement if relying on `@scope`, since browser support may vary. + +`@scope` is useful for limiting broad selectors to a subtree without increasing specificity. + +**Animations And Transitions** + +Use transitions for simple state changes: + +```css +.button { + transition: + background-color 150ms ease, + color 150ms ease, + border-color 150ms ease; +} +``` + +Prefer animating compositor-friendly properties: + +- `transform` +- `opacity` + +Avoid animating layout-heavy properties where possible: + +- `width` +- `height` +- `top` +- `left` +- `margin` +- `padding` + +Use `@keyframes` for defined animations: + +```css +@keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +.dialog { + animation: fade-in 180ms ease-out; +} +``` + +Use custom easing tokens: + +```css +:root { + --ease-out: cubic-bezier(0.16, 1, 0.3, 1); + --duration-fast: 150ms; +} +``` + +Respect reduced motion: + +```css +@media (prefers-reduced-motion: reduce) { + .dialog { + animation: none; + } +} +``` + +Use `transition-behavior: allow-discrete` for discrete transitions where supported: + +```css +.popover { + transition: + opacity 150ms ease, + display 150ms allow-discrete; +} +``` + +Use `@starting-style` for entry transitions where supported: + +```css +[popover]:popover-open { + opacity: 1; + + @starting-style { + opacity: 0; + } +} +``` + +Use progressive enhancement for newer transition features. + +Use scroll-driven animations progressively: + +```css +@supports (animation-timeline: scroll()) { + .progress { + animation: grow linear both; + animation-timeline: scroll(); + } +} +``` + +Scroll-driven animations are useful for progress indicators and reveal effects but should be optional and motion-safe. + +**Transforms** + +Common transforms: + +```css +translate +scale +rotate +skew +matrix +perspective +``` + +Prefer individual transform properties where useful: + +```css +.card:hover { + translate: 0 -2px; + scale: 1.01; +} +``` + +Instead of always composing: + +```css +transform: translateY(-2px) scale(1.01); +``` + +Use `transform-origin`: + +```css +.menu { + transform-origin: top right; +} +``` + +Transforms create stacking contexts and affect containing blocks for fixed/absolute descendants in some cases. + +**Filters, Blend Modes, Masks** + +Use filters carefully because they can be expensive: + +```css +.image { + filter: grayscale(1) contrast(1.1); +} +``` + +Common filters: + +```css +blur() +brightness() +contrast() +drop-shadow() +grayscale() +hue-rotate() +invert() +opacity() +saturate() +sepia() +``` + +Use `backdrop-filter` for translucent UI, with fallback: + +```css +.panel { + background: rgb(255 255 255 / 0.85); +} + +@supports (backdrop-filter: blur(12px)) { + .panel { + background: rgb(255 255 255 / 0.65); + backdrop-filter: blur(12px); + } +} +``` + +Use blend modes sparingly: + +```css +.overlay { + mix-blend-mode: multiply; +} +``` + +Use masks for advanced clipping where appropriate: + +```css +.fade { + mask-image: linear-gradient(black, transparent); +} +``` + +Provide fallbacks for important content. + +**Clipping And Shapes** + +Use `clip-path` for visual clipping: + +```css +.avatar { + clip-path: circle(); +} +``` + +Use `border-radius` for simple rounded clipping. + +Use `shape-outside` for text wrapping around floated shapes: + +```css +.figure { + float: inline-start; + shape-outside: circle(); +} +``` + +This is less common but valid for editorial layouts. + +**Containment And Performance** + +Use `contain` to isolate rendering/layout/paint when appropriate: + +```css +.widget { + contain: layout paint; +} +``` + +Values include: + +```css +size +layout +style +paint +content +strict +``` + +Use `content-visibility` for large offscreen sections: + +```css +.section { + content-visibility: auto; + contain-intrinsic-size: 600px; +} +``` + +This can improve initial rendering performance for long pages. + +Use `will-change` sparingly and temporarily: + +```css +.card { + will-change: transform; +} +``` + +Do not apply `will-change` broadly. It can increase memory use. + +Avoid layout thrash from CSS choices that force excessive reflow. + +Minimize expensive selectors across huge DOMs, though modern engines are generally efficient. + +Avoid unnecessary deeply nested selectors, universal selectors in hot subtrees, and broad `:has()` selectors when performance matters. + +**Accessibility** + +CSS should preserve accessibility. + +Do not remove focus indicators. + +Use `:focus-visible`: + +```css +:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} +``` + +Use reduced motion: + +```css +@media (prefers-reduced-motion: reduce) { + * { + scroll-behavior: auto; + } +} +``` + +Respect user color preferences: + +```css +@media (prefers-color-scheme: dark) { +} +``` + +Support forced colors: + +```css +@media (forced-colors: active) { + .button { + forced-color-adjust: auto; + } +} +``` + +Avoid hiding content from screen readers unintentionally. + +Visually hidden utility: + +```css +.visually-hidden { + position: absolute; + inline-size: 1px; + block-size: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; +} +``` + +Do not use `display: none` or `visibility: hidden` for content that should remain available to assistive tech. + +Use sufficient touch targets, generally around 44 by 44 CSS pixels where possible. + +Do not use CSS to reorder content in ways that break keyboard or screen-reader reading order. + +Be cautious with: + +```css +order +grid-area +position: absolute +``` + +Visual order should usually match DOM order. + +Use `prefers-contrast` where useful: + +```css +@media (prefers-contrast: more) { + :root { + --color-border: CanvasText; + } +} +``` + +Use `forced-color-adjust` only when you have a strong reason to opt out of system colors. + +**Hiding Content** + +Different hiding techniques have different effects. + +Remove visually and from accessibility tree: + +```css +.hidden { + display: none; +} +``` + +Hide visually but preserve layout: + +```css +.invisible { + visibility: hidden; +} +``` + +Hide visually but preserve for screen readers: + +```css +.sr-only { + position: absolute; + inline-size: 1px; + block-size: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; +} +``` + +Hide overflow: + +```css +.clipped { + overflow: hidden; +} +``` + +Use the right hiding technique for the intended behavior. + +**Logical Properties And Internationalization** + +Prefer logical properties: + +```css +margin-inline-start +margin-inline-end +margin-block-start +margin-block-end +padding-inline +padding-block +border-inline +border-block +inset-inline +inset-block +inline-size +block-size +min-inline-size +max-inline-size +min-block-size +max-block-size +``` + +Use logical values: + +```css +text-align: start; +float: inline-start; +clear: inline-end; +``` + +This improves support for RTL and vertical writing modes. + +Use `:dir()` for direction-specific styling: + +```css +:dir(rtl) .icon { + scale: -1 1; +} +``` + +Use `:lang()` for language-specific typography: + +```css +:lang(ja) { + line-break: strict; +} +``` + +**Theming** + +Use custom properties and attributes/classes for themes: + +```css +:root { + --color-bg: white; + --color-text: black; +} + +[data-theme='dark'] { + --color-bg: #0f172a; + --color-text: #f8fafc; + color-scheme: dark; +} +``` + +Use media query defaults: + +```css +@media (prefers-color-scheme: dark) { + :root { + --color-bg: #0f172a; + --color-text: #f8fafc; + color-scheme: dark; + } +} +``` + +Allow explicit user choice to override system preference. + +Avoid duplicating entire component styles for themes. Prefer token changes. + +**Component Styling** + +Good component CSS typically: + +- Has a single clear root class +- Uses custom properties for variants +- Keeps specificity low +- Avoids leaking styles globally +- Defines all relevant states +- Uses stable dimensions where needed +- Handles overflow and long content +- Works in different containers +- Does not rely on exact page placement + +Example: + +```css +.alert { + --alert-bg: var(--color-info-bg); + --alert-border: var(--color-info-border); + --alert-text: var(--color-info-text); + + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 0.75rem; + padding: 1rem; + border: 1px solid var(--alert-border); + border-radius: var(--radius-md); + background: var(--alert-bg); + color: var(--alert-text); +} + +.alert[data-tone='danger'] { + --alert-bg: var(--color-danger-bg); + --alert-border: var(--color-danger-border); + --alert-text: var(--color-danger-text); +} +``` + +Use data attributes for state and variants when appropriate: + +```css +.tabs [role='tab'][aria-selected='true'] { + color: var(--color-accent); +} +``` + +Styling ARIA states is appropriate, but ARIA should not be added only for styling. Use ARIA when it represents real accessibility state. + +**Utility Classes** + +Utility classes can be useful for common single-purpose styles. + +Examples: + +```css +.stack { + display: grid; + gap: var(--stack-gap, 1rem); +} + +.cluster { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--cluster-gap, 0.75rem); +} + +.center { + display: grid; + place-items: center; +} + +.wrapper { + inline-size: min(100% - 2rem, var(--wrapper-max, 72rem)); + margin-inline: auto; +} +``` + +Avoid creating uncontrolled utility sprawl without conventions. + +Use utilities for: + +- Layout primitives +- Spacing helpers +- Visually hidden content +- Text truncation +- Container wrappers +- Repeated alignment patterns + +**Common Layout Patterns** + +Stack: + +```css +.stack { + display: flex; + flex-direction: column; + gap: var(--gap, 1rem); +} +``` + +Cluster: + +```css +.cluster { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--gap, 0.75rem); +} +``` + +Sidebar: + +```css +.with-sidebar { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +.with-sidebar > :first-child { + flex: 0 0 16rem; +} + +.with-sidebar > :last-child { + flex: 1 1 0; + min-inline-size: min(100%, 24rem); +} +``` + +Responsive grid: + +```css +.auto-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); + gap: 1rem; +} +``` + +Holy grail layout: + +```css +.app { + min-block-size: 100dvh; + display: grid; + grid-template: + 'header header' auto + 'sidebar main' 1fr + / auto 1fr; +} +``` + +Centering: + +```css +.center { + display: grid; + place-items: center; +} +``` + +Media object: + +```css +.media-object { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 1rem; + align-items: start; +} +``` + +Sticky footer: + +```css +body { + min-block-size: 100dvh; + display: grid; + grid-template-rows: auto 1fr auto; +} +``` + +Text truncation: + +```css +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +``` + +Multi-line clamp: + +```css +.line-clamp { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} +``` + +Modern `line-clamp` support is improving, but prefixed syntax remains common. + +**State Styling** + +Use classes, data attributes, ARIA attributes, and pseudo-classes. + +```css +.menu[data-open='true'] { + display: block; +} + +.accordion-button[aria-expanded='true'] .icon { + rotate: 180deg; +} + +.form-field:has(:focus-visible) { + border-color: var(--color-focus); +} +``` + +Prefer state attributes that reflect real state. + +Avoid relying on class names like `.active` without clear ownership in large systems if attributes are more descriptive. + +**Dialogs, Popovers, And Top Layer** + +Style native dialogs: + +```css +dialog { + border: 0; + border-radius: var(--radius-md); + padding: 0; + max-inline-size: min(100% - 2rem, 40rem); +} + +dialog::backdrop { + background: rgb(0 0 0 / 0.5); +} +``` + +Use `:modal` where appropriate: + +```css +dialog:modal { + box-shadow: var(--shadow-lg); +} +``` + +Style popovers: + +```css +[popover] { + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + padding: 0.5rem; + background: var(--color-surface); +} + +[popover]:popover-open { + display: grid; +} +``` + +Use `@starting-style` and `transition-behavior` for progressive popover/dialog transitions. + +Top-layer elements such as modal dialogs and popovers are not controlled by ordinary z-index in the same way as normal page elements. + +**Anchor Positioning** + +CSS Anchor Positioning is a cutting-edge feature useful for positioning popovers/tooltips relative to an anchor. + +Conceptually: + +```css +.trigger { + anchor-name: --trigger; +} + +.tooltip { + position-anchor: --trigger; + inset-area: block-end; +} +``` + +Support has historically been limited, so use as progressive enhancement with JS or simpler positioning fallback. + +Use `@supports`: + +```css +@supports (anchor-name: --anchor) { + .trigger { + anchor-name: --trigger; + } +} +``` + +**View Transitions** + +The View Transitions API can animate page or state transitions with CSS pseudo-elements. + +Common CSS hooks include: + +```css +::view-transition +::view-transition-group() +::view-transition-image-pair() +::view-transition-old() +::view-transition-new() +``` + +Example: + +```css +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: 200ms; +} +``` + +Use progressive enhancement. Do not make navigation depend on view transitions. + +Respect reduced motion. + +**CSS And Shadow DOM** + +When styling web components: + +Use `:host`: + +```css +:host { + display: block; +} +``` + +Use host states: + +```css +:host([disabled]) { + opacity: 0.5; +} +``` + +Use `::slotted()` for slotted content: + +```css +::slotted(img) { + max-inline-size: 100%; +} +``` + +Use `::part()` to expose styleable internals: + +```css +custom-select::part(button) { + border-radius: var(--radius-sm); +} +``` + +Custom properties cross shadow boundaries and are useful for theming. + +**Feature Queries** + +Use `@supports` for progressive enhancement: + +```css +.card { + display: flex; +} + +@supports (display: grid) { + .card { + display: grid; + } +} +``` + +Use selector feature queries: + +```css +@supports selector(:has(*)) { + .field:has(input:invalid) { + border-color: red; + } +} +``` + +Use negative feature queries carefully: + +```css +@supports not (container-type: inline-size) { +} +``` + +Prefer a solid baseline fallback first, then enhanced styles. + +**Imports** + +Use `@import` sparingly in production CSS because it can delay loading. Prefer bundler-managed imports or HTML `<link>` where appropriate. + +If using `@import`, put it at the top before normal style rules: + +```css +@import url('./tokens.css') layer(tokens); +``` + +Modern `@import` can specify layers and supports: + +```css +@import url('./modern.css') supports(display: grid) layer(components); +``` + +**Print Styles** + +Add print styles for content-heavy pages: + +```css +@media print { + body { + color: black; + background: white; + } + + nav, + aside, + .no-print { + display: none; + } + + a[href]::after { + content: ' (' attr(href) ')'; + } +} +``` + +Avoid printing unnecessary UI. + +Ensure text is legible and backgrounds are not required. + +**CSS Syntax** + +Ruleset: + +```css +selector { + property: value; +} +``` + +Comments: + +```css +/* Comment */ +``` + +Custom properties: + +```css +--name: value; +property: var(--name); +``` + +At-rules: + +```css +@media {} +@supports {} +@container {} +@layer {} +@scope {} +@keyframes {} +@property {} +@font-face {} +@import +@charset +@page +``` + +Shorthands: + +```css +margin: 1rem; +padding: 1rem 2rem; +border: 1px solid currentColor; +background: white url('/x.png') center / cover no-repeat; +font: italic 700 1rem/1.5 system-ui; +``` + +Use shorthands carefully because they reset omitted subproperties. + +Example: + +```css +background: red; +``` + +This resets background image, position, size, repeat, and related longhands. + +Use longhands when preserving existing subproperties matters. + +**Selectors** + +Common selectors: + +```css +* /* universal */ +div /* type */ +.button /* class */ +#main /* id */ +[disabled] /* attribute */ +[type='button'] /* exact attribute */ +[href^='https'] /* starts with */ +[href$='.pdf'] /* ends with */ +[class*='icon'] /* contains */ +.parent .child /* descendant */ +.parent > .child /* direct child */ +.item + .item /* adjacent sibling */ +.item ~ .item /* general sibling */ +``` + +Prefer stable class/data selectors over styling incidental DOM. + +Avoid styling generated framework class names unless they are stable API. + +Use attribute selectors for state and variants: + +```css +.button[data-size='sm'] { +} +``` + +**Naming** + +Common naming approaches: + +- BEM-like: `.card`, `.card__title`, `.card--featured` +- Utility-first: `.flex`, `.gap-4`, `.text-sm` +- Component-scoped classes +- CSS Modules +- Data attributes for state +- Design-system tokens + +Whatever the naming convention, names should be: + +- Clear +- Stable +- Purposeful +- Not overly tied to current visual style +- Not overly tied to DOM depth + +Prefer `.product-card` over `.blue-box`. + +Prefer `.sidebar-nav` over `.left-stuff`. + +Avoid vague classes like `.thing`, `.box1`, `.new-style`. + +**Maintainability** + +Keep CSS organized by responsibility: + +- Reset/base +- Tokens +- Typography +- Layout primitives +- Components +- Utilities +- Overrides + +Avoid global leakage. + +Avoid magic numbers. Use named tokens or explain unavoidable values. + +Avoid duplicating large rule blocks. Use custom properties, shared classes, or component composition. + +Keep specificity flat. + +Use comments for non-obvious hacks or browser workarounds: + +```css +/* Prevent flex child from overflowing when the title is truncated. */ +.card-title { + min-inline-size: 0; +} +``` + +Remove dead CSS. + +Use formatting consistently. + +Use linting where available. + +Common tools: + +- Stylelint +- Prettier +- PostCSS +- Autoprefixer +- CSS Modules +- Sass, Less, or modern native CSS depending on stack +- Lightning CSS +- Bundler CSS pipelines + +Do not add a preprocessor just for nesting or variables now that native CSS supports many of those features, unless the project already uses one or needs its module/mixin tooling. + +**Preprocessors** + +Sass/Less remain useful for: + +- Build-time functions +- Loops +- Maps +- Mixins +- File organization +- Legacy browser support +- Existing codebases + +But prefer native CSS features when they meet the need: + +- Custom properties over Sass variables for runtime theming +- Native nesting over Sass nesting if supported by tooling +- Cascade layers over import-order hacks +- Container queries over breakpoint mixins for component responsiveness + +Avoid overusing mixins that hide emitted CSS complexity. + +**CSS Modules And Scoped CSS** + +CSS Modules prevent accidental global collisions. + +Example: + +```css +.card { + padding: 1rem; +} +``` + +Used from JS as an imported class map. + +Good for component applications. + +Still use semantic class names and design tokens. CSS Modules do not remove the need for good cascade and layout practices. + +**CSS-In-JS** + +CSS-in-JS can be useful for dynamic styling, colocated component styles, and design-system abstractions. + +Prefer static extraction where possible. + +Avoid generating excessive runtime styles. + +Use CSS variables for dynamic values instead of creating new classes/rules for every value. + +Example: + +```css +.card { + border-color: var(--card-border); +} +``` + +Then set: + +```html +<div class="card" style="--card-border: red"></div> +``` + +Use inline styles for truly dynamic one-off values, not for broad styling systems. + +**Utility-First CSS** + +Utility-first systems such as Tailwind can be effective. + +Best practices: + +- Use design tokens consistently +- Extract repeated component patterns when markup becomes noisy +- Avoid arbitrary values unless necessary +- Keep responsive and state variants readable +- Do not fight the framework with lots of ad hoc CSS +- Preserve semantic HTML and accessibility + +Utility-first CSS is still CSS; layout, cascade, specificity, and accessibility principles still apply. + +**Performance** + +CSS performance best practices: + +- Keep CSS payload reasonably small +- Remove unused CSS +- Avoid loading unused fonts and weights +- Avoid render-blocking CSS where possible +- Inline only critical CSS when it materially helps +- Avoid excessive expensive effects +- Prefer transform/opacity animations +- Use `content-visibility` for long pages where appropriate +- Avoid frequent layout changes during animation +- Use modern image sizing to avoid layout shifts +- Avoid broad, complex selectors in enormous documents +- Use containment for isolated widgets +- Avoid unnecessary `will-change` +- Use cascade layers and low specificity to reduce override bloat + +CSS is render-blocking by default, so file size and delivery strategy matter. + +**Browser Compatibility** + +Use progressive enhancement. + +Baseline approach: + +```css +.component { + /* broadly supported fallback */ +} + +@supports (new-feature: value) { + .component { + /* enhancement */ + } +} +``` + +Do not ship a feature without fallback if it affects core usability. + +Use build tools like Autoprefixer when supporting older browsers or features requiring prefixes. + +Vendor prefixes still appear for some features: + +```css +-webkit-line-clamp +-webkit-text-size-adjust +-webkit-font-smoothing +``` + +Avoid unnecessary prefixes in hand-authored CSS unless needed. + +Do not rely on browser-specific pseudo-elements for essential behavior unless there is a fallback. + +**Progressive Enhancement** + +Good progressive enhancement means: + +- The basic layout and content work everywhere you support +- Newer browsers get better layout, animation, color, or interaction +- Unsupported features fail harmlessly +- Critical functionality does not depend on decorative CSS +- Feature queries guard risky enhancements + +Examples: + +```css +.card { + display: block; +} + +@supports (display: grid) { + .card { + display: grid; + } +} +``` + +```css +.popover { + position: absolute; +} + +@supports (anchor-name: --x) { + .trigger { + anchor-name: --trigger; + } + + .popover { + position-anchor: --trigger; + } +} +``` + +**Common Modern Features I Would Consider Normal** + +These are modern CSS features I would generally know and consider usable in current development, depending on project browser targets: + +- Flexbox +- Grid +- Subgrid +- Container queries +- Container query units +- Cascade layers +- CSS nesting +- `:is()` +- `:where()` +- `:has()` +- `:focus-visible` +- Logical properties +- Dynamic viewport units +- `clamp()`, `min()`, `max()` +- `aspect-ratio` +- `gap` in flex and grid +- `color-mix()` +- `oklch()` / `oklab()` +- `accent-color` +- `color-scheme` +- `scroll-margin` +- `scroll-padding` +- `scroll-snap` +- `overscroll-behavior` +- `content-visibility` +- `contain-intrinsic-size` +- `@property` +- `@supports selector(...)` +- `@layer` +- `@container` +- Native dialog styling with `::backdrop` +- Popover styling with `:popover-open` +- `text-wrap: balance` +- `text-wrap: pretty` +- `line-clamp` +- Individual transform properties +- `:user-valid` / `:user-invalid` +- Range media query syntax + +**Cutting-Edge Features Suitable For Progressive Enhancement** + +Use these with fallbacks or feature queries: + +- CSS Anchor Positioning +- Scroll-driven animations +- View Transitions API CSS pseudo-elements +- `@scope` +- Advanced CSS math functions +- `transition-behavior: allow-discrete` +- `@starting-style` +- Advanced color functions where support varies +- Newer selectors and pseudo-classes where support may lag +- Masonry-style CSS grid features, where experimental +- Style queries for container queries, where supported + +**Common CSS APIs And At-Rules** + +`@media`: + +```css +@media (width >= 48rem) { +} +``` + +`@container`: + +```css +@container (inline-size > 30rem) { +} +``` + +`@supports`: + +```css +@supports (display: grid) { +} +``` + +`@layer`: + +```css +@layer base, components, utilities; +``` + +`@font-face`: + +```css +@font-face { + font-family: Example; + src: url('/example.woff2') format('woff2'); + font-display: swap; +} +``` + +`@keyframes`: + +```css +@keyframes spin { + to { + rotate: 1turn; + } +} +``` + +`@property`: + +```css +@property --angle { + syntax: '<angle>'; + inherits: false; + initial-value: 0deg; +} +``` + +`@scope`: + +```css +@scope (.component) { + h2 { + margin: 0; + } +} +``` + +`@page`: + +```css +@page { + margin: 1in; +} +``` + +**Common CSS Properties** + +Layout: + +```css +display +position +inset +inset-block +inset-inline +z-index +float +clear +contain +content-visibility +``` + +Box model: + +```css +box-sizing +inline-size +block-size +width +height +min-width +max-width +min-height +max-height +margin +padding +border +border-radius +outline +overflow +``` + +Flex: + +```css +flex +flex-basis +flex-grow +flex-shrink +flex-direction +flex-wrap +justify-content +align-items +align-content +align-self +order +gap +row-gap +column-gap +``` + +Grid: + +```css +grid +grid-template +grid-template-columns +grid-template-rows +grid-template-areas +grid-auto-flow +grid-auto-columns +grid-auto-rows +grid-column +grid-row +grid-area +justify-items +align-items +place-items +justify-content +align-content +place-content +``` + +Typography: + +```css +font +font-family +font-size +font-weight +font-style +font-stretch +line-height +letter-spacing +word-spacing +text-align +text-decoration +text-transform +text-indent +text-wrap +white-space +overflow-wrap +word-break +hyphens +font-kerning +font-feature-settings +font-variation-settings +``` + +Color and background: + +```css +color +background +background-color +background-image +background-position +background-size +background-repeat +background-clip +background-origin +opacity +color-scheme +accent-color +``` + +Visual effects: + +```css +box-shadow +text-shadow +filter +backdrop-filter +mix-blend-mode +background-blend-mode +clip-path +mask +``` + +Transforms and motion: + +```css +transform +translate +rotate +scale +transform-origin +transition +transition-property +transition-duration +transition-timing-function +transition-delay +animation +animation-name +animation-duration +animation-timing-function +animation-delay +animation-iteration-count +animation-direction +animation-fill-mode +animation-play-state +``` + +Scrolling: + +```css +scroll-behavior +scroll-margin +scroll-padding +scroll-snap-type +scroll-snap-align +scrollbar-gutter +overscroll-behavior +``` + +Interaction: + +```css +cursor +pointer-events +user-select +touch-action +caret-color +resize +appearance +``` + +Tables: + +```css +border-collapse +border-spacing +caption-side +table-layout +vertical-align +``` + +Lists: + +```css +list-style +list-style-type +list-style-position +list-style-image +``` + +Generated content: + +```css +content +counter-reset +counter-increment +counter-set +quotes +``` + +**Clean Code Principles For CSS** + +Use the least powerful selector that works. + +Prefer classes and attributes over IDs. + +Keep specificity low and consistent. + +Avoid deeply nested selectors. + +Avoid styling based on fragile DOM structure. + +Use semantic tokens. + +Use logical properties. + +Use layout primitives. + +Use modern layout instead of arbitrary positioning. + +Use progressive enhancement. + +Respect user preferences. + +Preserve accessibility defaults. + +Avoid global overrides unless they are intentional base styles. + +Keep related styles together. + +Keep utility classes single-purpose. + +Avoid magic numbers. + +Document hacks. + +Delete unused CSS. + +Prefer native CSS features over extra abstractions when sufficient. + +Make responsive behavior intrinsic where possible. + +Test narrow widths, long text, zoom, keyboard focus, reduced motion, dark mode, and high contrast. + +**Common Mistakes** + +Removing focus outlines without replacement. + +Using fixed heights for dynamic content. + +Using `100vh` on mobile without considering browser UI. + +Forgetting `min-width: 0` in flex/grid children. + +Using color alone for state. + +Overusing `position: absolute`. + +Overusing `!important`. + +Using IDs for styling. + +Creating high-specificity selectors that require more overrides later. + +Animating layout properties unnecessarily. + +Forgetting reduced-motion preferences. + +Using background images for meaningful content. + +Not reserving image/media space, causing layout shift. + +Making visual order differ from DOM order. + +Using hover-only interactions. + +Ignoring RTL/writing mode issues. + +Using `overflow: hidden` to mask layout bugs. + +Styling native controls so heavily that accessibility suffers. + +Loading excessive font files. + +Failing to handle long words, long names, or translated strings. + +Assuming viewport width equals component width. + +Putting all responsive behavior in global breakpoints instead of using container queries. + +**Default Practical CSS Baseline** + +A reasonable default baseline I would commonly apply: + +```css +@layer reset, base, layout, components, utilities; + +@layer reset { + *, + *::before, + *::after { + box-sizing: border-box; + } + + body, + h1, + h2, + h3, + h4, + p, + figure, + blockquote, + dl, + dd { + margin: 0; + } + + img, + picture, + svg, + canvas, + video { + display: block; + max-inline-size: 100%; + } + + button, + input, + textarea, + select { + font: inherit; + } +} + +@layer base { + :root { + color-scheme: light; + --color-bg: #ffffff; + --color-text: #111827; + --color-muted: #6b7280; + --color-border: #d1d5db; + --color-focus: #2563eb; + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + } + + body { + min-block-size: 100dvh; + background: var(--color-bg); + color: var(--color-text); + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + line-height: 1.5; + } + + :focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; + } + + a { + color: inherit; + text-underline-offset: 0.15em; + } +} + +@layer layout { + .wrapper { + inline-size: min(100% - 2rem, 72rem); + margin-inline: auto; + } + + .stack { + display: grid; + gap: var(--stack-gap, 1rem); + } + + .cluster { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--cluster-gap, 0.75rem); + } + + .auto-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); + gap: var(--grid-gap, 1rem); + } +} + +@layer utilities { + .visually-hidden { + position: absolute; + inline-size: 1px; + block-size: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + } + + .truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + } +} +``` + +**Default Decision Rules** + +Use grid when both rows and columns matter. + +Use flexbox when arranging items along one axis. + +Use container queries when a component responds to its own space. + +Use media queries when the whole page responds to the viewport or environment. + +Use custom properties for themeable runtime values. + +Use Sass/build-time variables only for compile-time abstractions. + +Use native controls unless custom controls are necessary. + +Use semantic HTML and then style it. + +Use progressive enhancement for newer CSS. + +Use low specificity by default. + +Use logical properties by default. + +Use `rem`, `em`, `%`, `fr`, `ch`, and modern viewport units appropriately. + +Use fixed pixel values mostly for borders, icons, hairlines, and precise control dimensions. + +Use `aspect-ratio` to reserve media space. + +Use `minmax(0, 1fr)` and `min-inline-size: 0` to prevent overflow in grid/flex layouts. + +Use visible focus styles. + +Use reduced motion handling. + +Use dark mode and forced-color awareness when relevant. + +Use clean tokens and avoid repeating raw values across components. diff --git a/.agents/skills/project-discipline-guides/mirrors/css/gemini_mirror.md b/.agents/skills/project-discipline-guides/mirrors/css/gemini_mirror.md new file mode 100644 index 000000000..13d9c1ec0 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/css/gemini_mirror.md @@ -0,0 +1,74 @@ +# CSS Comprehensive Best Practices & Modern API Guide (Redundancy Mirror) + +## 1. Modern Syntax & Selectors +* **Native Nesting**: Use browser-native nesting. Avoid deep nesting (max 3 levels) to prevent specificity bloat and maintain readability. + ```css + .card { + padding: 1rem; + & .title { font-weight: bold; } + @media (width > 600px) { padding: 2rem; } + } + ``` +* **Logical Selectors**: + * `:is()`: Reduces repetition in complex selectors without increasing specificity. + * `:where()`: Similar to `:is()` but with **zero specificity**, ideal for resets and base styles. + * `:has()`: The "parent selector." Use for relational styling (e.g., `.card:has(img) { ... }`). +* **Pseudo-classes**: Prefer `:focus-visible` over `:focus` for better UX. Use `:not()` to exclude elements without complex overrides. + +## 2. Architecture & The Cascade +* **Cascade Layers (`@layer`)**: Organize styles into explicit layers (e.g., `reset`, `base`, `components`, `utilities`) to solve specificity wars. +* **Custom Properties (Variables)**: + * Use for tokens (colors, spacing, typography). + * Scope variables locally when possible. + * Use `@property` for typed variables to enable animation of custom properties. +* **Scoped CSS**: Use `@scope (.component) to (.slots)` for native component encapsulation (progressive enhancement). + +## 3. Layout & Responsiveness +* **CSS Grid**: + * Use `grid-template-areas` for readable layouts. + * `repeat(auto-fit, minmax(size, 1fr))` for intrinsic responsiveness without media queries. + * **Subgrid**: Use `grid-template-columns: subgrid` to align nested elements to the parent grid. +* **Flexbox**: Use `gap` for spacing instead of margins on children. +* **Container Queries**: Use `@container` (inline-size) for component-driven responsiveness based on the parent's size rather than the viewport. +* **Logical Properties**: Use `margin-inline`, `padding-block`, `inset-inline-start` instead of top/bottom/left/right to support RTL/LTR automatically. +* **Box Sizing**: Always use `box-sizing: border-box`. + +## 4. Units & Sizing +* **Relative Units**: + * `rem` for typography and spacing (accessibility). + * `ch` for limiting line length (ideal for readability, ~65-75ch). + * `vh`/`vw` and the new viewport units: `svh`, `lvh`, `dvh` (to handle mobile browser UI shifts). +* **Math Functions**: + * `clamp(min, preferred, max)` for fluid typography and sizing. + * `calc()`, `min()`, and `max()` for complex calculations. +* **Aspect Ratio**: Use the `aspect-ratio` property instead of padding hacks. + +## 5. Colors & Theming +* **Modern Color Spaces**: Prefer `oklch()` or `lch()` for perceptually uniform colors and access to wider gamuts. +* **Color Functions**: + * `color-mix(in oklch, color1, color2 percentage)` for dynamic tinting/shading. + * Relative color syntax: `oklch(from var(--primary) l c h / 0.5)` for transparency. +* **System Colors**: Use `accent-color` for native form elements. +* **Dark Mode**: Use `light-dark(lightValue, darkValue)` or `prefers-color-scheme`. + +## 6. Typography +* **Variable Fonts**: Utilize `font-variation-settings` for fine-grained control over weight, slant, etc. +* **Text Wrapping**: + * `text-wrap: balance` for headings (prevents orphans). + * `text-wrap: pretty` for body text (better hyphenation/line breaks). +* **Font Display**: Always use `font-display: swap`. + +## 7. Interactions & Animations +* **Transitions**: Use `transition: property duration timing-function`. Prefer `prefers-reduced-motion` media queries. +* **View Transitions**: Use the View Transitions API for seamless page/state transitions. +* **Scroll-Driven Animations**: Use `scroll-timeline` and `view-timeline` for scroll-linked effects without JS. +* **Performance**: Use `will-change` sparingly for complex animations. Prefer `transform` and `opacity` to avoid layout thrashing. + +## 8. Performance & Accessibility +* **Content Visibility**: Use `content-visibility: auto` for off-screen elements to improve rendering performance. +* **Containment**: Use `contain: layout paint` to limit browser reflows. +* **A11y Standards**: + * Maintain high color contrast (WCAG 2.1). + * Never remove `outline` without a `:focus-visible` replacement. + * Use `forced-colors` media query for high-contrast mode support. +* **Images**: Use `object-fit` and `aspect-ratio` to prevent layout shifts (CLS). Use `image-set()` for responsive backgrounds. diff --git a/.agents/skills/project-discipline-guides/mirrors/css/mirror.md b/.agents/skills/project-discipline-guides/mirrors/css/mirror.md new file mode 100644 index 000000000..b9fefa5ce --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/css/mirror.md @@ -0,0 +1,64 @@ +# CSS Unified Lowest Common Denominator (LCD) Knowledge Mirror + +## 1. Modern Syntax & Selectors +* **Native Nesting**: Use browser-native nesting with the `&` selector to improve locality and readability. +* **Logical Selectors**: + * `:is()`: Group multiple selectors while preserving the specificity of the most specific argument. + * `:where()`: Group selectors with **zero specificity**, ideal for resets and base styles. + * `:has()`: Use as a "parent selector" or relational selector to style elements based on their children or state. + * `:not()`: Exclude specific elements from a selector. +* **Pseudo-classes**: Prefer `:focus-visible` over `:focus` to ensure focus indicators only appear for keyboard users. + +## 2. Architecture & The Cascade +* **Cascade Layers (`@layer`)**: Organize styles into explicit layers (e.g., `reset`, `base`, `components`) to manage the cascade independently of selector specificity. +* **Custom Properties (CSS Variables)**: + * Use for design tokens (colors, spacing, typography). + * Define global tokens at the `:root` level and scope component-specific variables locally. +* **Typed Custom Properties (`@property`)**: Register variables with specific types (syntax) to enable smooth transitions and animations. + +## 3. Layout & Responsiveness +* **Flexbox**: Use for one-dimensional layouts. Utilize `gap` for consistent spacing between children. +* **CSS Grid**: + * Use for two-dimensional layouts. + * Employ `repeat(auto-fit, minmax(size, 1fr))` for intrinsic responsiveness. + * **Subgrid**: Use `grid-template-columns: subgrid` or `grid-template-rows: subgrid` to align nested elements with the parent's grid tracks. +* **Container Queries (`@container`)**: Apply styles based on a parent container's size rather than the viewport. +* **Logical Properties**: Prefer logical properties (e.g., `margin-inline`, `padding-block`, `inset-inline`) over physical ones to support different writing modes and directions automatically. +* **Box Model**: Always use `box-sizing: border-box` to ensure padding and borders are included in element dimensions. + +## 4. Units & Sizing +* **Relative Units**: + * `rem`: Use for typography and spacing to respect user font size preferences. + * `ch`: Use for limiting line length to improve readability. +* **Viewport Units**: Use `vh` and `vw`, as well as newer units `svh`, `lvh`, and `dvh` to handle mobile browser UI shifts. +* **Math Functions**: Use `calc()`, `min()`, `max()`, and `clamp()` for fluid typography and responsive sizing constraints. +* **Aspect Ratio**: Use the `aspect-ratio` property to reserve space for media and prevent layout shifts. + +## 5. Colors & Theming +* **Modern Color Spaces**: Use `oklch()` or `lch()` for perceptually uniform color definitions. +* **Color Functions**: + * `color-mix()`: Derivatively tint or shade colors dynamically. +* **System Controls**: Use `accent-color` to apply theme colors to native form elements. +* **Dark Mode**: Utilize `prefers-color-scheme` media queries to support user system preferences. + +## 6. Typography +* **Variable Fonts**: Use to control multiple font axes (weight, slant, etc.) with a single file. +* **Text Wrapping**: + * `text-wrap: balance`: Prevents orphans in headings. + * `text-wrap: pretty`: Optimizes line breaks and hyphenation for body text. +* **Font Loading**: Always set `font-display: swap` to ensure text remains visible during font loading. + +## 7. Interactions & Animations +* **Transitions & Keyframes**: Use standard `transition` and `@keyframes` for state changes and animations. +* **View Transitions**: Use the View Transitions API for seamless state or page transitions. +* **Scroll-Driven Animations**: Use properties like `scroll-timeline` or `view-timeline` to link animations to scroll progress without JavaScript. +* **Performance**: Prefer animating `transform` and `opacity` to avoid layout thrashing. Use `will-change` sparingly for complex animations. + +## 8. Performance & Accessibility +* **Rendering Optimization**: + * `content-visibility: auto`: Improves rendering performance for off-screen elements. + * `contain`: Use to limit the scope of browser reflows and repaints. +* **Accessibility Standards**: + * Maintain high color contrast ratios. + * Support user preferences via `prefers-reduced-motion` and `forced-colors` media queries. +* **Layout Stability**: Use `object-fit` and `aspect-ratio` on images to prevent Cumulative Layout Shift (CLS). diff --git a/.agents/skills/project-discipline-guides/mirrors/forms/claude_mirror.md b/.agents/skills/project-discipline-guides/mirrors/forms/claude_mirror.md new file mode 100644 index 000000000..d4073c331 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/forms/claude_mirror.md @@ -0,0 +1,692 @@ +# Forms Development: Common Knowledge Guide + +## 1. Semantic HTML Foundation + +### The `<form>` Element + +Always wrap form controls in a `<form>` element, even for single-input forms or JS-driven submissions. This enables implicit submission (Enter key), browser autofill heuristics, and assistive technology recognition. + +```html +<form action="/submit" method="post" novalidate> + <!-- controls --> +</form> +``` + +**Attributes:** +- `action` — submission URL +- `method` — `get` (default) or `post` +- `enctype` — `application/x-www-form-urlencoded` (default), `multipart/form-data` (for file uploads), or `text/plain` +- `novalidate` — disables native browser validation (use when implementing custom validation) +- `autocomplete` — `on` (default) or `off` +- `target` — `_self`, `_blank`, etc. +- `name` — identifies the form + +### Always Pair Inputs with Labels + +Every form control must have an associated label. Two valid patterns: + +```html +<!-- Explicit association --> +<label for="email">Email</label> +<input type="email" id="email" name="email"> + +<!-- Implicit association (wrapping) --> +<label> + Email + <input type="email" name="email"> +</label> +``` + +Prefer explicit `for`/`id` association — it works in more contexts and is more reliable with assistive tech. + +**Never** substitute `placeholder` for a label. Placeholders disappear on input, fail contrast requirements, and are not announced consistently by screen readers. + +### Fieldsets and Legends + +Group related controls — especially radio buttons and checkboxes — with `<fieldset>` and `<legend>`: + +```html +<fieldset> + <legend>Shipping speed</legend> + <label><input type="radio" name="speed" value="standard"> Standard</label> + <label><input type="radio" name="speed" value="express"> Express</label> +</fieldset> +``` + +## 2. Input Types + +Use the most specific input type available — browsers provide tailored UI, virtual keyboards, and validation: + +| Type | Use case | +|------|----------| +| `text` | Generic single-line text | +| `email` | Email addresses (validates format) | +| `tel` | Phone numbers (numeric keyboard on mobile) | +| `url` | URLs (validates format) | +| `number` | Numeric input with spinner | +| `password` | Masked input | +| `search` | Search field (clear button on some platforms) | +| `date`, `time`, `datetime-local`, `month`, `week` | Date/time pickers | +| `color` | Color picker | +| `range` | Slider | +| `file` | File upload | +| `checkbox` | Boolean toggle | +| `radio` | Mutually exclusive choice | +| `hidden` | Form data not shown to user | + +### Type-Specific Attributes + +```html +<input type="number" min="0" max="100" step="0.01" inputmode="decimal"> +<input type="text" minlength="3" maxlength="20" pattern="[a-z0-9]+"> +<input type="file" accept="image/*" multiple capture="environment"> +<input type="email" multiple> +<input type="date" min="2024-01-01" max="2030-12-31"> +``` + +## 3. Critical Attributes + +### `name` Attribute + +Required for the control's value to be included in form submission. Without `name`, the field is excluded. + +### `autocomplete` — Use Specific Tokens + +Provide accurate `autocomplete` values for autofill and password managers. Never use `autocomplete="off"` for personal info — it's often ignored and harms UX. + +```html +<input type="text" autocomplete="given-name"> +<input type="text" autocomplete="family-name"> +<input type="email" autocomplete="email"> +<input type="tel" autocomplete="tel"> +<input type="text" autocomplete="street-address"> +<input type="text" autocomplete="postal-code"> +<input type="text" autocomplete="cc-number"> +<input type="text" autocomplete="cc-exp"> +<input type="password" autocomplete="current-password"> +<input type="password" autocomplete="new-password"> +<input type="text" autocomplete="one-time-code"> +``` + +For section grouping: `autocomplete="section-shipping street-address"`. + +### `inputmode` — Virtual Keyboard Hint + +Independent of `type`, controls the on-screen keyboard: + +```html +<input type="text" inputmode="numeric" pattern="[0-9]*"> +<input type="text" inputmode="decimal"> +<input type="text" inputmode="email"> +<input type="text" inputmode="tel"> +<input type="text" inputmode="url"> +<input type="search" inputmode="search"> +``` + +### `enterkeyhint` — Enter Key Label + +```html +<input enterkeyhint="search"> +<input enterkeyhint="send"> +<input enterkeyhint="done"> +<input enterkeyhint="next"> +<input enterkeyhint="go"> +``` + +### `required`, `disabled`, `readonly` + +- `required` — must have a value to submit +- `disabled` — not focusable, not submitted, greyed out +- `readonly` — focusable, value submitted, not editable + +Disabled fields are excluded from submission. Use `readonly` if you need the value in the payload. + +## 4. Native Validation + +### Constraint Validation Attributes + +```html +<input required> +<input minlength="8" maxlength="100"> +<input min="0" max="100" step="1"> +<input pattern="[A-Za-z0-9]+" title="Alphanumeric only"> +<input type="email"> <!-- format validation --> +<input type="url"> <!-- format validation --> +``` + +### The Constraint Validation API + +```js +input.checkValidity(); // boolean +input.reportValidity(); // boolean + shows native UI +input.validity; // ValidityState object +input.validationMessage; // localized message +input.setCustomValidity(''); // set custom error or clear + +// ValidityState properties +input.validity.valueMissing; +input.validity.typeMismatch; +input.validity.patternMismatch; +input.validity.tooLong; +input.validity.tooShort; +input.validity.rangeUnderflow; +input.validity.rangeOverflow; +input.validity.stepMismatch; +input.validity.badInput; +input.validity.customError; +input.validity.valid; +``` + +### `:invalid`, `:valid`, `:user-invalid`, `:user-valid` + +Prefer `:user-invalid` / `:user-valid` over `:invalid` / `:valid` — they only apply after the user has interacted with the field, avoiding the "everything is red on page load" anti-pattern. Now supported in all modern browsers. + +```css +input:user-invalid { + border-color: red; +} +input:user-valid { + border-color: green; +} +``` + +### Custom Validation Pattern + +```js +form.addEventListener('submit', (e) => { + if (!form.checkValidity()) { + e.preventDefault(); + // surface errors + } +}); + +input.addEventListener('input', () => { + if (input.value === 'reserved') { + input.setCustomValidity('That value is reserved.'); + } else { + input.setCustomValidity(''); + } +}); +``` + +Always disable native UI with `novalidate` on the form when implementing custom error messaging, but still call `checkValidity()` to leverage the API. + +## 5. Accessibility + +### Error Messaging + +Associate error messages with inputs via `aria-describedby` and mark invalid inputs with `aria-invalid`: + +```html +<label for="email">Email</label> +<input + type="email" + id="email" + name="email" + aria-invalid="true" + aria-describedby="email-error"> +<p id="email-error" role="alert">Please enter a valid email address.</p> +``` + +For dynamic error announcement, use `role="alert"` or a live region (`aria-live="polite"`). + +### Required Fields + +The `required` attribute implicitly conveys required state. To add a visual indicator (e.g., asterisk), ensure it's also conveyed: + +```html +<label for="name">Name <span aria-hidden="true">*</span></label> +<input id="name" name="name" required aria-required="true"> +``` + +`aria-required="true"` is redundant with `required` but harmless. + +### Focus Management + +- On submit error, move focus to the first invalid field. +- After successful submission, move focus to the success message or the next logical control. +- Never trap focus inside a form. + +### Hidden Labels + +If a visible label isn't possible, use `aria-label` or `aria-labelledby` — but always prefer a visible label. + +```html +<input type="search" aria-label="Search products"> +``` + +### Touch Targets + +Interactive controls should have a minimum touch target of 44×44 CSS pixels (WCAG 2.5.5). + +### Color Contrast + +Form labels, placeholder text, error messages, and borders must meet WCAG AA contrast: 4.5:1 for text, 3:1 for UI components. + +## 6. Layout and Styling + +### Native Control Styling + +Modern CSS allows extensive customization of form controls: + +```css +/* Reset native appearance */ +input, button, select, textarea { + appearance: none; + -webkit-appearance: none; + font: inherit; + color: inherit; +} + +/* Custom checkbox/radio with accent-color (cross-browser) */ +input[type="checkbox"], input[type="radio"] { + accent-color: rebeccapurple; +} + +/* Caret color */ +input { caret-color: hotpink; } + +/* Selection color in inputs */ +input::selection { background: yellow; } +``` + +### Pseudo-elements + +```css +input::placeholder { color: #888; } +input::file-selector-button { /* style file input button */ } +input[type="search"]::-webkit-search-cancel-button { display: none; } +``` + +### Field Sizing (Modern) + +```css +textarea { + field-sizing: content; /* Auto-grows with content - Chromium */ +} +``` + +### Form Layout + +Use CSS Grid or Flexbox for form layout. A common pattern: + +```css +form { + display: grid; + gap: 1rem; +} + +.field { + display: grid; + gap: 0.25rem; +} +``` + +### `<dialog>` for Modal Forms + +```html +<dialog id="confirm"> + <form method="dialog"> + <p>Are you sure?</p> + <button value="cancel">Cancel</button> + <button value="confirm">Confirm</button> + </form> +</dialog> +``` + +`<form method="dialog">` closes the dialog on submit and exposes the clicked button's value via `dialog.returnValue`. + +## 7. Buttons + +### Always Specify `type` + +Default `<button>` type inside a `<form>` is `submit`. Explicitly set `type="button"` for non-submitting buttons to prevent surprise submissions: + +```html +<button type="submit">Save</button> +<button type="button" onclick="...">Cancel</button> +<button type="reset">Reset</button> +``` + +### Multiple Submit Buttons + +Use `name` and `value` to distinguish: + +```html +<button type="submit" name="action" value="save">Save</button> +<button type="submit" name="action" value="publish">Publish</button> +``` + +Or `formaction`/`formmethod` for per-button overrides. + +### Button vs. Link + +- `<button>` triggers an action +- `<a>` navigates to a URL + +Don't style a `<div>` as a button — use the right element. If you must, add `role="button"`, `tabindex="0"`, and keyboard handlers, but don't. + +## 8. Form Submission + +### `FormData` API + +```js +form.addEventListener('submit', async (e) => { + e.preventDefault(); + const formData = new FormData(form); + + // Iterate + for (const [key, value] of formData) { /* ... */ } + + // Convert to plain object (loses multi-value fields) + const data = Object.fromEntries(formData); + + // Submit as multipart + await fetch('/api', { method: 'POST', body: formData }); + + // Submit as JSON + await fetch('/api', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(Object.fromEntries(formData)), + }); + + // Submit as URL-encoded + await fetch('/api', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams(formData), + }); +}); +``` + +### `FormData` for File Uploads + +```js +const formData = new FormData(); +formData.append('file', fileInput.files[0]); +formData.append('description', 'My file'); +// Don't set Content-Type — browser sets it with the boundary +await fetch('/upload', { method: 'POST', body: formData }); +``` + +### Form Events + +- `submit` — fires on submission attempt; cancel with `preventDefault()` +- `formdata` — fires after `submit`, allows mutating the `FormData` +- `reset` — fires when the form is reset +- `input` — fires on every value change +- `change` — fires when value commits (blur for text inputs) +- `invalid` — fires on a control when validation fails + +### Idempotent Submissions + +Disable the submit button (or show a spinner) during submission to prevent double-submits: + +```js +form.addEventListener('submit', async (e) => { + e.preventDefault(); + const submitBtn = form.querySelector('[type="submit"]'); + submitBtn.disabled = true; + try { + await submit(new FormData(form)); + } finally { + submitBtn.disabled = false; + } +}); +``` + +## 9. Security + +### Always Validate Server-Side + +Client-side validation is for UX only. Re-validate every input on the server. Never trust: +- `disabled` (can be removed in DevTools) +- `maxlength` (can be bypassed) +- `pattern` (can be bypassed) +- Hidden field values (visible and editable) +- File `accept` (file type must be checked server-side) + +### CSRF Protection + +For state-changing requests with cookie-based auth, include a CSRF token: + +```html +<input type="hidden" name="csrf_token" value="..."> +``` + +Or use `SameSite=Strict`/`Lax` cookies. + +### Input Sanitization + +- Never `innerHTML` user input — use `textContent` or `Element.setHTML()` (Sanitizer API). +- Use parameterized queries server-side; never string-concatenate SQL. +- Escape output appropriate to context (HTML, JS, URL, CSS). + +### Password Fields + +```html +<input + type="password" + autocomplete="new-password" + minlength="12" + required> +``` + +- `new-password` for sign-up/change-password — triggers password manager generation +- `current-password` for login +- Don't enforce arbitrary complexity rules; long passphrases beat complex short ones (NIST guidance) +- Allow paste — disabling it breaks password managers + +### One-Time Codes + +```html +<input + type="text" + inputmode="numeric" + autocomplete="one-time-code" + pattern="[0-9]{6}" + maxlength="6"> +``` + +iOS/Android will surface SMS codes for autofill. + +### Sensitive Inputs + +- `autocomplete="off"` for credit card CVC +- Use HTTPS — never submit credentials over HTTP +- Consider `Content-Security-Policy` to limit script sources + +## 10. Modern APIs and Features + +### `<form>` Association for Custom Elements + +Custom elements can participate in forms via `ElementInternals`: + +```js +class MyInput extends HTMLElement { + static formAssociated = true; + #internals = this.attachInternals(); + setValue(v) { this.#internals.setFormValue(v); } + setValidity(...args) { this.#internals.setValidity(...args); } +} +``` + +### `formAction` / `formMethod` / `formTarget` / `formEnctype` / `formNoValidate` + +Per-button overrides of the parent form's attributes. + +### `requestSubmit()` + +Programmatically submit a form **with** validation (unlike `.submit()`): + +```js +form.requestSubmit(); +form.requestSubmit(submitButton); // Simulates clicking that button +``` + +### `URLSearchParams` + +```js +const params = new URLSearchParams(window.location.search); +params.get('q'); +params.set('page', '2'); +history.pushState(null, '', '?' + params); +``` + +### `submitter` on the Submit Event + +Identifies which button triggered submission: + +```js +form.addEventListener('submit', (e) => { + console.log(e.submitter); // The clicked button +}); +``` + +## 11. UX Best Practices + +### Field Order + +Match the user's mental model — for addresses, follow local convention. For sign-up, ask for the minimum needed. + +### Error Display + +- Inline errors next to the relevant field +- Show errors on blur (or after submit attempt), not on every keystroke +- Use clear, actionable language: "Enter a valid email address" not "Invalid input" +- Don't clear field values on validation error +- Summarize errors at the top of long forms with anchors to fields + +### Progressive Disclosure + +Reveal additional fields conditionally rather than overwhelming users upfront. + +### Save Progress + +For long forms, save drafts to `localStorage` so users don't lose data: + +```js +input.addEventListener('input', () => { + localStorage.setItem(`form:${input.name}`, input.value); +}); +``` + +Clear on successful submit. + +### Loading States + +Show explicit loading state during async operations. Disable submit, show a spinner, announce status with `aria-live`. + +### Confirmation + +After successful submit, show clear confirmation. For destructive actions, require confirmation before submission (`<dialog>` works well). + +### Avoid Modal Form Anti-patterns + +- Don't auto-advance on input (jarring, breaks paste) +- Don't split phone/SSN/credit-card across multiple inputs unless paste is handled (use `inputmode` and let users paste a full string) +- Don't strip formatting characters silently +- Don't truncate without warning + +## 12. Common Patterns + +### Toggle Password Visibility + +```html +<div class="password-field"> + <input type="password" id="pw" name="pw"> + <button type="button" aria-pressed="false" aria-label="Show password">👁</button> +</div> +``` + +```js +btn.addEventListener('click', () => { + const isShown = btn.getAttribute('aria-pressed') === 'true'; + btn.setAttribute('aria-pressed', String(!isShown)); + pw.type = isShown ? 'password' : 'text'; +}); +``` + +### Character Counter + +```html +<textarea id="bio" maxlength="280" aria-describedby="bio-count"></textarea> +<p id="bio-count" aria-live="polite">280 characters remaining</p> +``` + +### Combobox / Autocomplete + +Use `<input list="...">` with `<datalist>` for simple cases (free text + suggestions): + +```html +<input list="cities" name="city"> +<datalist id="cities"> + <option value="Berlin"> + <option value="Boston"> +</datalist> +``` + +For richer comboboxes, use the ARIA combobox pattern (`role="combobox"`, `aria-expanded`, `aria-controls`, `aria-activedescendant`). + +### Multi-Step Forms + +- One logical group per step +- Show progress (`aria-valuenow` on a progressbar, or a stepper) +- Allow back navigation without losing state +- Validate per step on "next" + +## 13. Clean Code Principles + +### Single Source of Truth + +Don't duplicate validation logic in HTML attributes and JS. Prefer reading from the DOM (`input.validity`) or define rules in one place. + +### Avoid Deeply Nested Conditionals + +Validation logic should be flat and readable: + +```js +// Prefer +if (!input.value) return setError('Required'); +if (input.value.length < 8) return setError('Too short'); + +// Over nested if/else trees +``` + +### Extract Reusable Logic + +Validation, error display, and submission logic that repeat across forms belong in shared functions or custom elements. + +### Naming + +- `name` attributes match the server's expected field names +- `id` values are unique per page +- Use `kebab-case` for HTML, `camelCase` for JS, snake_case for backend conventions + +### Don't Reinvent Native Behavior + +Native form controls are accessible, internationalized, and keyboard-navigable for free. Wrap before you replace. If you must build a custom control: +- Match keyboard expectations (Space activates checkboxes, Arrow keys navigate radios, etc.) +- Implement focus management +- Support form participation via `ElementInternals` +- Test with screen readers + +### Progressive Enhancement + +Forms should work without JavaScript when feasible: +- `<form action>` should point to a working endpoint +- JS enhances the submission (no page reload, better errors) but isn't required +- This is also a resilience pattern — JS can fail to load + +### Accessibility is Not Optional + +Test with keyboard only (Tab, Shift+Tab, Enter, Space, Arrow keys). Test with a screen reader (VoiceOver, NVDA, JAWS, TalkBack). Run automated tools (axe, Lighthouse) but don't rely on them exclusively. + +### Internationalization + +- Use `inputmode` for keyboard, not assumptions about character sets +- Support locale-specific date/number/address formats +- Don't constrain names to ASCII or specific length +- Allow international phone numbers (`type="tel"` is intentionally unstructured) +- Email validation: be permissive — emails like `user+tag@subdomain.example.museum` are valid diff --git a/.agents/skills/project-discipline-guides/mirrors/forms/codex_mirror.md b/.agents/skills/project-discipline-guides/mirrors/forms/codex_mirror.md new file mode 100644 index 000000000..d7c248f79 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/forms/codex_mirror.md @@ -0,0 +1,2792 @@ +# Redundancy Mirror: Forms Development Common Knowledge + +This is a broad “common knowledge” guide for modern web form development, based only on inherent knowledge. It focuses on standards-based HTML, CSS, JavaScript, browser APIs, accessibility, validation, progressive enhancement, security, and clean implementation patterns that I would normally apply by default. + +## Core Principles + +Use native platform features first. + +Prefer semantic HTML form controls over custom widgets. Native controls provide keyboard support, accessibility semantics, mobile keyboard optimization, validation hooks, autofill integration, high-contrast support, screen reader support, and browser consistency for free. + +Progressively enhance. + +A form should ideally work with plain HTML submission first, then gain JavaScript behavior for client-side validation, inline feedback, autosave, optimistic UI, async submission, or richer controls. + +Do not replace native behavior unless necessary. + +Avoid custom selects, checkboxes, radios, date inputs, sliders, and file pickers unless there is a strong product requirement. If custom controls are unavoidable, implement the full keyboard, focus, ARIA, validation, and form-submission behavior. + +Validate on both client and server. + +Client-side validation improves UX. Server-side validation is authoritative. Never rely on client-side validation for security, authorization, data integrity, spam prevention, or business rules. + +Make invalid states understandable. + +Users should know what failed, where it failed, why it failed, and how to fix it. Error messages should be specific and attached to the relevant field. + +Use real labels. + +Every form control needs an accessible name, usually from a visible `<label>`. Placeholders are not labels. + +Preserve user input. + +Avoid clearing fields after errors. Preserve entered values, selections, focus context, and scroll position where possible. + +Design for interruptions. + +Users may navigate away, refresh, submit twice, lose connectivity, use autofill, paste unusual content, zoom, use assistive tech, or interact from touch devices. + +## HTML Form Structure + +Use `<form>` for form submission boundaries. + +```html +<form method="post" action="/signup"> + ... +</form> +``` + +Use meaningful `method`. + +```html +<form method="get" action="/search"> + <input name="q" type="search"> + <button type="submit">Search</button> +</form> +``` + +Use `GET` for safe, idempotent query-like actions such as search and filtering. + +Use `POST` for creating, mutating, authenticating, uploading, or sending private data. + +Always specify button type. + +```html +<button type="submit">Save</button> +<button type="button">Cancel</button> +<button type="reset">Reset</button> +``` + +Inside forms, `<button>` defaults to `type="submit"`, which often causes accidental submissions. + +Use `name` on successful controls. + +Only controls with a `name` participate in native form submission and `FormData`. + +```html +<input name="email" type="email"> +``` + +Use stable machine-readable names. + +```html +<input name="firstName"> +<input name="billingAddress.postalCode"> +``` + +Avoid names tied to presentation such as `leftInput` or `box1`. + +Use `<fieldset>` and `<legend>` for grouped controls. + +```html +<fieldset> + <legend>Notification preferences</legend> + + <label> + <input type="checkbox" name="notifications" value="email"> + Email + </label> + + <label> + <input type="checkbox" name="notifications" value="sms"> + SMS + </label> +</fieldset> +``` + +Use fieldsets especially for radio groups, checkbox groups, address sections, payment sections, and multi-part questions. + +Use `<label>` correctly. + +Explicit label: + +```html +<label for="email">Email</label> +<input id="email" name="email" type="email"> +``` + +Implicit label: + +```html +<label> + Email + <input name="email" type="email"> +</label> +``` + +Explicit labels are often easier to style and work reliably with complex layouts. + +Do not use placeholder as the only label. + +```html +<label for="email">Email</label> +<input id="email" name="email" type="email" placeholder="you@example.com"> +``` + +Use help text outside the placeholder. + +```html +<label for="password">Password</label> +<p id="password-help">Use at least 12 characters.</p> +<input id="password" name="password" type="password" aria-describedby="password-help"> +``` + +Use one form per logical submit target. + +Avoid wrapping unrelated sections in one large form if they submit independently. + +Use `form` attribute when controls live outside the form element. + +```html +<form id="profile-form" action="/profile" method="post"> + <input name="displayName"> +</form> + +<button type="submit" form="profile-form">Save</button> +``` + +Use `autocomplete` intentionally. + +```html +<input name="email" type="email" autocomplete="email"> +<input name="given-name" autocomplete="given-name"> +<input name="family-name" autocomplete="family-name"> +<input name="current-password" type="password" autocomplete="current-password"> +<input name="new-password" type="password" autocomplete="new-password"> +``` + +Avoid disabling autocomplete unless necessary. Password managers and autofill are user agents that users rely on. + +Use `enctype="multipart/form-data"` for file uploads. + +```html +<form method="post" enctype="multipart/form-data"> + <input type="file" name="avatar"> +</form> +``` + +Use `accept` as a hint, not validation. + +```html +<input type="file" name="avatar" accept="image/png,image/jpeg"> +``` + +Server must still validate file type, size, content, and security. + +Use `multiple` when appropriate. + +```html +<input type="file" name="photos" multiple> +<select name="tags" multiple> +``` + +Use `disabled` and `readonly` correctly. + +`disabled` controls are not submitted and are usually skipped by keyboard focus. + +```html +<input name="plan" value="pro" disabled> +``` + +`readonly` controls are submitted and focusable for some input types. + +```html +<input name="email" value="user@example.com" readonly> +``` + +Use `hidden` inputs for non-user-editable submission data, but never trust them. + +```html +<input type="hidden" name="csrfToken" value="..."> +``` + +Hidden inputs can be modified by clients. + +## Input Types + +Use the most specific appropriate input type. + +Text: + +```html +<input type="text" name="fullName"> +``` + +Email: + +```html +<input type="email" name="email" autocomplete="email"> +``` + +Password: + +```html +<input type="password" name="password" autocomplete="current-password"> +``` + +Search: + +```html +<input type="search" name="q"> +``` + +URL: + +```html +<input type="url" name="website"> +``` + +Telephone: + +```html +<input type="tel" name="phone" autocomplete="tel"> +``` + +Number: + +```html +<input type="number" name="quantity" min="1" max="99" step="1"> +``` + +Date: + +```html +<input type="date" name="startDate"> +``` + +Time: + +```html +<input type="time" name="startTime"> +``` + +Datetime local: + +```html +<input type="datetime-local" name="meetingTime"> +``` + +Month: + +```html +<input type="month" name="billingMonth"> +``` + +Week: + +```html +<input type="week" name="deliveryWeek"> +``` + +Color: + +```html +<input type="color" name="themeColor"> +``` + +Range: + +```html +<input type="range" name="volume" min="0" max="100" step="1"> +``` + +Checkbox: + +```html +<label> + <input type="checkbox" name="terms" value="accepted" required> + I agree to the terms +</label> +``` + +Radio: + +```html +<fieldset> + <legend>Plan</legend> + + <label> + <input type="radio" name="plan" value="basic" required> + Basic + </label> + + <label> + <input type="radio" name="plan" value="pro"> + Pro + </label> +</fieldset> +``` + +File: + +```html +<input type="file" name="resume" accept=".pdf,.doc,.docx"> +``` + +Hidden: + +```html +<input type="hidden" name="source" value="newsletter"> +``` + +Submit: + +```html +<button type="submit">Submit</button> +``` + +Avoid `input type="submit"` when a `<button>` is more flexible. + +Use `inputmode` for better mobile keyboards. + +```html +<input name="zip" inputmode="numeric" autocomplete="postal-code"> +<input name="price" inputmode="decimal"> +<input name="phone" type="tel" inputmode="tel"> +<input name="email" type="email" inputmode="email"> +``` + +Use `type="text"` plus `inputmode` for numeric identifiers that are not numbers. + +Examples: ZIP codes, credit card numbers, account numbers, one-time codes. + +```html +<input + name="postalCode" + type="text" + inputmode="numeric" + autocomplete="postal-code" +> +``` + +Do not use `type="number"` for values where leading zeroes, fixed length, formatting, or non-arithmetic semantics matter. + +Use `autocomplete="one-time-code"` for OTP fields. + +```html +<input + name="code" + type="text" + inputmode="numeric" + autocomplete="one-time-code" + maxlength="6" +/> +``` + +Use `enterkeyhint` when useful on mobile. + +```html +<input type="search" name="q" enterkeyhint="search"> +<input name="email" enterkeyhint="next"> +``` + +Common values include `enter`, `done`, `go`, `next`, `previous`, `search`, and `send`. + +Use `autocapitalize` for text behavior. + +```html +<input name="name" autocapitalize="words"> +<input name="email" type="email" autocapitalize="none"> +``` + +Use `spellcheck` where appropriate. + +```html +<textarea name="message" spellcheck="true"></textarea> +<input name="username" spellcheck="false" autocapitalize="none"> +``` + +## Textarea + +Use `<textarea>` for multi-line text. + +```html +<label for="bio">Bio</label> +<textarea id="bio" name="bio" rows="5" maxlength="500"></textarea> +``` + +Do not put the initial value in a `value` attribute. + +```html +<textarea name="comment">Initial text</textarea> +``` + +Use `rows` to provide a reasonable initial height. + +Use character counters only when limits matter. + +Use `maxlength` for hard client-side limits, but enforce limits on the server too. + +Use CSS resizing intentionally. + +```css +textarea { + resize: vertical; +} +``` + +## Select, Datalist, and Options + +Use `<select>` for constrained choices. + +```html +<label for="country">Country</label> +<select id="country" name="country" required> + <option value="">Select a country</option> + <option value="us">United States</option> + <option value="ca">Canada</option> +</select> +``` + +Use an empty first option for required selects when no default should be chosen. + +Use meaningful submitted values. + +```html +<option value="standard">Standard shipping</option> +<option value="express">Express shipping</option> +``` + +Use `<optgroup>` for grouped options. + +```html +<select name="timezone"> + <optgroup label="North America"> + <option value="America/New_York">Eastern Time</option> + <option value="America/Chicago">Central Time</option> + </optgroup> +</select> +``` + +Use `<datalist>` for suggestions, not strict selection. + +```html +<label for="city">City</label> +<input id="city" name="city" list="city-options"> + +<datalist id="city-options"> + <option value="New York"> + <option value="Los Angeles"> + <option value="Chicago"> +</datalist> +``` + +A datalist allows arbitrary input. Validate accordingly. + +## Validation Attributes + +Use native validation attributes where appropriate. + +Required: + +```html +<input name="email" type="email" required> +``` + +Length: + +```html +<input name="username" minlength="3" maxlength="20"> +``` + +Numeric range: + +```html +<input name="age" type="number" min="18" max="120"> +``` + +Step: + +```html +<input name="quantity" type="number" min="1" step="1"> +``` + +Pattern: + +```html +<input + name="username" + pattern="[A-Za-z0-9_]{3,20}" + title="Use 3 to 20 letters, numbers, or underscores." +> +``` + +Use `pattern` carefully. It is easy to make regex validation too strict, especially for names, addresses, phone numbers, emails, and international input. + +Use built-in type validation when possible. + +```html +<input type="email" name="email"> +<input type="url" name="website"> +``` + +Use `novalidate` when replacing native validation UI with custom validation, while still using the Constraint Validation API. + +```html +<form novalidate> + ... +</form> +``` + +Avoid disabling validation without replacing it. + +Use `formnovalidate` for alternate submit actions. + +```html +<button type="submit">Publish</button> +<button type="submit" formnovalidate name="intent" value="draft"> + Save draft +</button> +``` + +## Constraint Validation API + +Use `checkValidity()` to test validity and fire invalid events. + +```js +const form = document.querySelector("form"); + +if (!form.checkValidity()) { + // invalid events fire on controls +} +``` + +Use `reportValidity()` to show browser validation UI. + +```js +form.reportValidity(); +``` + +Use `setCustomValidity()` for custom messages. + +```js +const input = document.querySelector("#username"); + +input.setCustomValidity(""); + +if (input.value.includes(" ")) { + input.setCustomValidity("Usernames cannot contain spaces."); +} +``` + +Always clear custom validity when the value becomes valid. + +```js +input.addEventListener("input", () => { + input.setCustomValidity(""); + + if (input.value && input.value.length < 3) { + input.setCustomValidity("Use at least 3 characters."); + } +}); +``` + +Use `validity` for precise states. + +```js +if (input.validity.valueMissing) { + input.setCustomValidity("Enter your email address."); +} else if (input.validity.typeMismatch) { + input.setCustomValidity("Enter a valid email address."); +} else { + input.setCustomValidity(""); +} +``` + +Common `ValidityState` properties: + +```js +input.validity.valueMissing; +input.validity.typeMismatch; +input.validity.patternMismatch; +input.validity.tooShort; +input.validity.tooLong; +input.validity.rangeUnderflow; +input.validity.rangeOverflow; +input.validity.stepMismatch; +input.validity.badInput; +input.validity.customError; +input.validity.valid; +``` + +Use `validationMessage` for the current message. + +```js +console.log(input.validationMessage); +``` + +Use `invalid` event for field-level handling. + +```js +form.addEventListener( + "invalid", + (event) => { + const field = event.target; + field.classList.add("is-invalid"); + }, + true +); +``` + +The `invalid` event does not bubble, so use capture if listening on the form. + +Use `requestSubmit()` instead of `submit()` for programmatic submission. + +```js +form.requestSubmit(); +``` + +`requestSubmit()` behaves like clicking a submit button: it runs validation and submit event handlers. + +`form.submit()` bypasses validation and submit handlers. + +Use `SubmitEvent.submitter` to identify which button submitted the form. + +```js +form.addEventListener("submit", (event) => { + const button = event.submitter; + const intent = button?.value; +}); +``` + +Use custom validity for cross-field validation. + +```js +const password = form.elements.password; +const confirmPassword = form.elements.confirmPassword; + +function validatePasswords() { + confirmPassword.setCustomValidity(""); + + if (confirmPassword.value && confirmPassword.value !== password.value) { + confirmPassword.setCustomValidity("Passwords do not match."); + } +} + +password.addEventListener("input", validatePasswords); +confirmPassword.addEventListener("input", validatePasswords); +``` + +Validate cross-field constraints at submission too. + +```js +form.addEventListener("submit", (event) => { + validatePasswords(); + + if (!form.reportValidity()) { + event.preventDefault(); + } +}); +``` + +## FormData + +Use `FormData` to read form values in a standards-based way. + +```js +const formData = new FormData(form); +``` + +Get a single value: + +```js +const email = formData.get("email"); +``` + +Get repeated values: + +```js +const tags = formData.getAll("tags"); +``` + +Set a value: + +```js +formData.set("email", "user@example.com"); +``` + +Append repeated values: + +```js +formData.append("tags", "javascript"); +``` + +Delete a value: + +```js +formData.delete("draft"); +``` + +Iterate entries: + +```js +for (const [name, value] of formData) { + console.log(name, value); +} +``` + +Convert simple forms to object carefully. + +```js +const data = Object.fromEntries(new FormData(form)); +``` + +This loses repeated values such as checkbox groups and multi-selects. + +Handle repeated values explicitly. + +```js +const formData = new FormData(form); + +const data = { + email: formData.get("email"), + interests: formData.getAll("interests"), +}; +``` + +Use the `formdata` event to augment native submissions. + +```js +form.addEventListener("formdata", (event) => { + event.formData.append("clientTimestamp", new Date().toISOString()); +}); +``` + +Use `new FormData(form, submitter)` when submitter-specific data matters, where supported. + +```js +const formData = new FormData(form, event.submitter); +``` + +Use `URLSearchParams` for GET-style query bodies when files are not involved. + +```js +const params = new URLSearchParams(new FormData(form)); +location.href = `/search?${params}`; +``` + +Do not use `URLSearchParams` for `File` values. + +## Async Submission + +Intercept submit with `submit` event. + +```js +form.addEventListener("submit", async (event) => { + event.preventDefault(); + + if (!form.reportValidity()) return; + + const formData = new FormData(form); + + const response = await fetch(form.action, { + method: form.method, + body: formData, + }); + + if (!response.ok) { + // show error + return; + } + + // show success +}); +``` + +Let the browser set `Content-Type` for `FormData`. + +Do not manually set `Content-Type: multipart/form-data`; the browser must include the boundary. + +Use JSON only when the server expects JSON and files are not needed. + +```js +const data = Object.fromEntries(new FormData(form)); + +await fetch(form.action, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), +}); +``` + +Include credentials intentionally. + +```js +await fetch("/profile", { + method: "POST", + body: new FormData(form), + credentials: "same-origin", +}); +``` + +Use `AbortController` to cancel stale submissions. + +```js +let currentController; + +form.addEventListener("submit", async (event) => { + event.preventDefault(); + + currentController?.abort(); + + const controller = new AbortController(); + currentController = controller; + + try { + await fetch(form.action, { + method: "POST", + body: new FormData(form), + signal: controller.signal, + }); + } catch (error) { + if (error.name !== "AbortError") throw error; + } +}); +``` + +Disable submit controls during active submission. + +```js +const submitButton = form.querySelector('[type="submit"]'); + +form.addEventListener("submit", async (event) => { + event.preventDefault(); + + submitButton.disabled = true; + + try { + await submitForm(); + } finally { + submitButton.disabled = false; + } +}); +``` + +Avoid permanently trapping users in a disabled state. + +Use idempotency keys for operations that must not run twice. + +```html +<input type="hidden" name="idempotencyKey" value="..."> +``` + +Server must enforce idempotency. + +Handle double-clicks and repeated submissions. + +Use `aria-busy` to communicate loading state. + +```html +<form aria-busy="false"> + ... +</form> +``` + +```js +form.setAttribute("aria-busy", "true"); +form.setAttribute("aria-busy", "false"); +``` + +Use optimistic UI only when rollback is clear. + +For critical operations such as payments, account deletion, security changes, or booking, prefer confirmed server response before showing completion. + +## Error Handling + +Show field-level errors near fields. + +```html +<label for="email">Email</label> +<input id="email" name="email" type="email" aria-describedby="email-error"> +<p id="email-error" class="field-error"></p> +``` + +Set `aria-invalid` when invalid. + +```js +input.setAttribute("aria-invalid", "true"); +``` + +Remove it when valid. + +```js +input.removeAttribute("aria-invalid"); +``` + +Use `aria-describedby` to associate help and error text. + +```html +<input + id="password" + name="password" + type="password" + aria-describedby="password-help password-error" +> +<p id="password-help">Use at least 12 characters.</p> +<p id="password-error"></p> +``` + +Use a summary for multiple errors. + +```html +<div role="alert" tabindex="-1" id="error-summary"> + <h2>Fix the following errors</h2> + <ul> + <li><a href="#email">Enter a valid email address.</a></li> + </ul> +</div> +``` + +Move focus to an error summary after failed submission when helpful. + +```js +errorSummary.focus(); +``` + +Do not move focus on every keystroke. + +Use live regions sparingly. + +```html +<p id="form-status" role="status"></p> +``` + +`role="status"` is polite. `role="alert"` is assertive and should be reserved for urgent or submission-level errors. + +Avoid only using color to indicate errors. + +Pair color with text, icons, borders, or messages. + +Error messages should be actionable. + +Poor: + +```text +Invalid input. +``` + +Better: + +```text +Enter an email address in the format name@example.com. +``` + +Do not expose sensitive server details in errors. + +For authentication, avoid revealing whether the username or password was wrong. + +```text +The email or password is incorrect. +``` + +## Accessibility + +Every control needs an accessible name. + +Use visible labels for normal fields. + +Use `aria-label` only when a visible label is impractical. + +```html +<input type="search" name="q" aria-label="Search"> +``` + +Prefer `aria-labelledby` when visible text exists. + +```html +<h2 id="shipping-heading">Shipping address</h2> +<section aria-labelledby="shipping-heading"> + ... +</section> +``` + +Do not use ARIA to override native semantics unless necessary. + +Native: + +```html +<button type="submit">Save</button> +``` + +Avoid: + +```html +<div role="button" tabindex="0">Save</div> +``` + +Use real buttons for actions and links for navigation. + +```html +<button type="button">Open menu</button> +<a href="/settings">Settings</a> +``` + +Maintain keyboard support. + +Users should be able to tab through fields, activate controls, select radio/checkbox options, submit, and recover from errors using the keyboard. + +Do not remove visible focus outlines without replacing them. + +```css +:focus-visible { + outline: 2px solid Highlight; + outline-offset: 2px; +} +``` + +Use `:focus-visible` for keyboard-focused styling. + +```css +button:focus-visible, +input:focus-visible, +select:focus-visible, +textarea:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; +} +``` + +Use logical focus order. + +DOM order should generally match visual order. + +Avoid positive `tabindex`. + +```html +<!-- Avoid --> +<input tabindex="3"> +``` + +Use `tabindex="-1"` for programmatic focus targets like error summaries. + +```html +<div id="error-summary" tabindex="-1"></div> +``` + +Respect zoom and text resizing. + +Avoid fixed heights that clip text. Use flexible layouts and adequate line-height. + +Target touch sizes should be comfortable. + +Interactive controls should generally have a target size around 44 CSS pixels or more where practical. + +Use sufficient color contrast. + +Text, borders used as the only indicator, error messages, placeholders, and disabled states should remain readable. + +Do not make disabled controls so low-contrast that users cannot understand them. + +Use `aria-required` only when native `required` cannot be used. + +```html +<input required> +``` + +Use native `required` for real form fields. + +Use `aria-invalid`. + +```html +<input aria-invalid="true"> +``` + +Use `aria-errormessage` carefully. + +```html +<input aria-invalid="true" aria-errormessage="email-error"> +<p id="email-error">Enter a valid email address.</p> +``` + +`aria-describedby` has broader practical use for help and errors. + +Do not announce every character or validation state aggressively. + +Validate on blur or submit for many fields. Use live validation on input only when the feedback is genuinely useful and not noisy. + +For radio groups, the group label should be in `<legend>`. + +For custom composite widgets, follow established ARIA patterns. + +Examples: combobox, listbox, date picker, slider, switch. Implement keyboard interaction fully or use a well-tested library. + +## CSS for Forms + +Use `box-sizing: border-box`. + +```css +*, +*::before, +*::after { + box-sizing: border-box; +} +``` + +Use readable default styling. + +```css +input, +select, +textarea, +button { + font: inherit; +} +``` + +Use `display: block` or layout utilities for stacked fields. + +```css +.form-field { + display: grid; + gap: 0.375rem; +} +``` + +Use adequate spacing. + +Group label, input, help text, and error text clearly. + +Avoid relying on placeholder color as primary text. + +```css +::placeholder { + color: color-mix(in srgb, currentColor 55%, transparent); +} +``` + +Use modern selectors where supported. + +```css +.field:has(input:required) label::after { + content: " *"; +} +``` + +Use `:has()` as progressive enhancement if needed. + +```css +.field:has(:invalid) { + border-color: red; +} +``` + +Be careful with `:invalid`; empty required fields match `:invalid` before interaction. + +Use user-interaction-aware patterns. + +```css +input:user-invalid { + border-color: red; +} +``` + +Where `:user-invalid` is unavailable or inconsistent, use JS-added classes after blur or submit. + +```css +.was-submitted input:invalid { + border-color: red; +} +``` + +Use `accent-color` for native checkboxes, radios, and ranges. + +```css +input[type="checkbox"], +input[type="radio"] { + accent-color: #0f766e; +} +``` + +Use `color-scheme` if supporting dark UI. + +```css +:root { + color-scheme: light dark; +} +``` + +This lets native controls adapt to light/dark modes. + +Style disabled states accessibly. + +```css +:disabled { + cursor: not-allowed; + opacity: 0.65; +} +``` + +Do not use opacity alone if it makes text unreadable. + +Use `appearance: none` cautiously. + +```css +.custom-input { + appearance: none; +} +``` + +Removing native appearance can remove useful affordances. Rebuild focus, disabled, checked, invalid, and high-contrast states. + +Use `resize: vertical` for textareas. + +```css +textarea { + resize: vertical; +} +``` + +Avoid fixed widths. + +```css +input, +select, +textarea { + width: 100%; + max-width: 100%; +} +``` + +Use logical properties. + +```css +.field { + margin-block-end: 1rem; +} +``` + +Use container queries or responsive layout where forms sit in variable-width containers. + +```css +.form-grid { + container-type: inline-size; +} + +@container (min-width: 40rem) { + .form-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} +``` + +Avoid layout shifts when errors appear. + +Reserve space if necessary, or place errors predictably below controls. + +Use `min-height` for error slots only when it improves stability. + +## Modern CSS Selectors and States + +Use `:required` and `:optional`. + +```css +input:required { + ... +} +``` + +Use `:valid` and `:invalid` carefully. + +```css +input:invalid { + ... +} +``` + +Use `:placeholder-shown` for floating labels only with care. + +```css +input:placeholder-shown + label { + ... +} +``` + +Floating labels can harm usability if labels become too small or if autofill creates state bugs. + +Use `:disabled`, `:enabled`, `:read-only`, `:read-write`. + +```css +input:read-only { + background: Canvas; +} +``` + +Use `:checked`. + +```css +input[type="radio"]:checked { + ... +} +``` + +Use `:indeterminate` for partially selected checkbox states. + +```js +checkbox.indeterminate = true; +``` + +```css +input:indeterminate { + ... +} +``` + +Use `:focus-within` for field wrappers. + +```css +.field:focus-within { + outline: 1px solid currentColor; +} +``` + +Use `:focus-visible` for keyboard focus. + +Use `:has()` for parent styling. + +```css +.field:has(input[aria-invalid="true"]) { + ... +} +``` + +Use `@supports selector(:has(*))` if needed. + +```css +@supports selector(:has(*)) { + .field:has(:invalid) { + ... + } +} +``` + +## Autofill and Password Managers + +Use correct `autocomplete` tokens. + +Common tokens: + +```html +autocomplete="name" +autocomplete="honorific-prefix" +autocomplete="given-name" +autocomplete="additional-name" +autocomplete="family-name" +autocomplete="honorific-suffix" +autocomplete="nickname" +autocomplete="username" +autocomplete="new-password" +autocomplete="current-password" +autocomplete="one-time-code" +autocomplete="organization-title" +autocomplete="organization" +autocomplete="street-address" +autocomplete="address-line1" +autocomplete="address-line2" +autocomplete="address-line3" +autocomplete="address-level1" +autocomplete="address-level2" +autocomplete="address-level3" +autocomplete="address-level4" +autocomplete="country" +autocomplete="country-name" +autocomplete="postal-code" +autocomplete="cc-name" +autocomplete="cc-given-name" +autocomplete="cc-family-name" +autocomplete="cc-number" +autocomplete="cc-exp" +autocomplete="cc-exp-month" +autocomplete="cc-exp-year" +autocomplete="cc-csc" +autocomplete="cc-type" +autocomplete="transaction-currency" +autocomplete="transaction-amount" +autocomplete="language" +autocomplete="bday" +autocomplete="bday-day" +autocomplete="bday-month" +autocomplete="bday-year" +autocomplete="sex" +autocomplete="url" +autocomplete="photo" +autocomplete="tel" +autocomplete="tel-country-code" +autocomplete="tel-national" +autocomplete="tel-area-code" +autocomplete="tel-local" +autocomplete="tel-extension" +autocomplete="email" +autocomplete="impp" +``` + +Use section prefixes to distinguish repeated groups. + +```html +<input autocomplete="section-billing street-address"> +<input autocomplete="section-shipping street-address"> +``` + +Do not block paste into password or OTP fields. + +Do not block password managers with unusual field names or fake inputs. + +Do not set `autocomplete="off"` on login fields unless there is a very specific reason. + +For new password forms: + +```html +<input name="username" autocomplete="username"> +<input name="newPassword" type="password" autocomplete="new-password"> +``` + +For login forms: + +```html +<input name="username" autocomplete="username"> +<input name="password" type="password" autocomplete="current-password"> +``` + +Use `name`, `id`, labels, and autocomplete values that password managers can understand. + +## Internationalization + +Do not over-constrain human names. + +Names may include spaces, apostrophes, hyphens, accents, non-Latin scripts, single names, long names, and different ordering. + +Do not assume addresses have the same fields in all countries. + +Address formats vary by country. Postal codes may contain letters, spaces, hyphens, or be absent. + +Do not assume phone numbers are numeric-only. + +Phone numbers can include `+`, spaces, parentheses, extensions, and country-specific formatting. + +Use `type="tel"` for phone input. + +```html +<input name="phone" type="tel" autocomplete="tel"> +``` + +Do not use `type="number"` for phone numbers. + +Use locale-aware formatting for display. + +```js +new Intl.NumberFormat(locale, { + style: "currency", + currency: "USD", +}).format(amount); +``` + +Use `Intl.DateTimeFormat` for display dates. + +```js +new Intl.DateTimeFormat(locale, { + dateStyle: "medium", +}).format(date); +``` + +Be careful with native date inputs. + +`input[type="date"]` submits a normalized `YYYY-MM-DD` value but displays according to browser locale. This is usually good, but server parsing must expect the submitted format. + +Handle time zones explicitly. + +`datetime-local` does not include a time zone. Treat it as local wall-clock time and combine with known user or business timezone server-side when needed. + +Use `dir="auto"` where user-generated text may be bidirectional. + +```html +<input name="displayName" dir="auto"> +<textarea name="message" dir="auto"></textarea> +``` + +Avoid hard-coded English-only validation patterns. + +Use translatable labels, help text, error messages, and status messages. + +## Security + +Server-side validation is mandatory. + +Validate and sanitize all submitted data on the server. + +Use CSRF protection for state-changing requests when authentication cookies are involved. + +```html +<input type="hidden" name="csrfToken" value="..."> +``` + +Use SameSite cookies where appropriate. + +Use HTTPS for forms that submit sensitive data. + +Never log sensitive form data unnecessarily. + +Sensitive data includes passwords, tokens, payment details, government IDs, health data, private messages, and authentication answers. + +Do not store passwords in client-side state longer than necessary. + +Do not echo raw submitted HTML back into the page. + +Escape output to prevent XSS. + +Use Content Security Policy as defense in depth. + +Use rate limiting and abuse detection for public forms. + +Use bot mitigation carefully. + +Avoid inaccessible CAPTCHAs where possible. Consider rate limits, honeypots, proof-of-work, email verification, or risk-based checks. + +Do not trust hidden fields. + +Do not trust disabled fields. + +Do not trust select options or radio values. + +Clients can modify all submitted data. + +For file uploads, validate: + +```text +size +MIME type +file extension +actual file signature/content +image dimensions when relevant +malware risk +storage location +authorization +``` + +Store uploads outside executable web roots when possible. + +Generate server-side file names. + +Strip metadata when appropriate. + +For authentication forms, use generic errors. + +For password reset flows, avoid revealing whether an account exists. + +For payment forms, prefer hosted payment fields or provider SDKs to reduce compliance scope. + +## Privacy + +Collect the minimum data needed. + +Explain why sensitive data is required. + +Avoid optional tracking fields unless necessary. + +Do not prefill sensitive information in shared contexts. + +Use `autocomplete` appropriately, but avoid exposing private data in the wrong form. + +Avoid storing form drafts containing sensitive information without user awareness. + +Use secure storage practices. + +Do not put sensitive data in URLs. + +GET query parameters may appear in history, logs, analytics, referrers, and screenshots. + +## Performance + +Keep forms lightweight. + +Avoid heavy client-side libraries for simple forms. + +Debounce expensive validation. + +```js +function debounce(callback, delay) { + let timeoutId; + + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => callback(...args), delay); + }; +} +``` + +Use server-side uniqueness checks sparingly and asynchronously. + +```js +const checkUsername = debounce(async () => { + ... +}, 300); +``` + +Cancel stale async validation with `AbortController`. + +Avoid validating every keystroke with network requests. + +Use native inputs instead of large custom widgets. + +Lazy-load complex controls such as rich text editors, address autocomplete, or payment SDKs. + +Avoid layout shifts from validation messages. + +Avoid blocking initial rendering with form enhancement scripts. + +Use event delegation for large dynamic forms. + +```js +form.addEventListener("input", (event) => { + if (event.target.matches("[data-validate]")) { + validateField(event.target); + } +}); +``` + +## Clean JavaScript Patterns + +Select forms by stable hooks. + +```html +<form data-profile-form> +``` + +```js +const form = document.querySelector("[data-profile-form]"); +``` + +Avoid coupling JS to presentation classes if those classes are likely to change. + +Use `form.elements`. + +```js +const email = form.elements.email; +``` + +Remember that `form.elements.name` can conflict with form properties. Bracket access is safer for unusual names. + +```js +const field = form.elements["billing.postalCode"]; +``` + +Keep validation functions pure where practical. + +```js +function getEmailError(value) { + if (!value) return "Enter your email address."; + if (!value.includes("@")) return "Enter a valid email address."; + return ""; +} +``` + +Separate state, validation, rendering, and submission. + +```js +function validate(form) { + ... +} + +function renderErrors(errors) { + ... +} + +async function submit(data) { + ... +} +``` + +Use early returns. + +```js +form.addEventListener("submit", async (event) => { + event.preventDefault(); + + if (!form.reportValidity()) return; + + await submitForm(form); +}); +``` + +Handle errors explicitly. + +```js +try { + await submitForm(form); +} catch (error) { + showFormError("Something went wrong. Try again."); +} +``` + +Do not swallow errors silently. + +Use `addEventListener`, not inline event handlers. + +```js +button.addEventListener("click", handleClick); +``` + +Avoid: + +```html +<button onclick="handleClick()">Save</button> +``` + +Clean up event listeners when components unmount. + +```js +const controller = new AbortController(); + +input.addEventListener("input", handleInput, { + signal: controller.signal, +}); + +controller.abort(); +``` + +Use `dataset` for declarative behavior. + +```html +<input data-min-age="18"> +``` + +```js +const minAge = Number(input.dataset.minAge); +``` + +Be careful with type conversion. + +```js +const age = Number(formData.get("age")); + +if (!Number.isFinite(age)) { + ... +} +``` + +Use `valueAsNumber` for numeric/date-like inputs when appropriate. + +```js +const amount = input.valueAsNumber; +``` + +Use `valueAsDate` for compatible date inputs. + +```js +const date = dateInput.valueAsDate; +``` + +Know that empty numeric inputs produce `NaN` for `valueAsNumber`. + +Use `matches`, `closest`, and event delegation. + +```js +form.addEventListener("click", (event) => { + const removeButton = event.target.closest("[data-remove-item]"); + if (!removeButton) return; + + removeButton.closest("[data-item]").remove(); +}); +``` + +Use templates for dynamic fields. + +```html +<template id="phone-template"> + <div data-phone-row> + <label> + Phone + <input name="phones[]" type="tel"> + </label> + <button type="button" data-remove-phone>Remove</button> + </div> +</template> +``` + +```js +const template = document.querySelector("#phone-template"); +const clone = template.content.cloneNode(true); +container.append(clone); +``` + +When adding dynamic controls, ensure unique IDs. + +```js +const id = `phone-${crypto.randomUUID()}`; +label.htmlFor = id; +input.id = id; +``` + +Use `crypto.randomUUID()` where available for unique client IDs. + +## Submission Intents + +Use named submit buttons for multiple actions. + +```html +<button type="submit" name="intent" value="save">Save</button> +<button type="submit" name="intent" value="publish">Publish</button> +``` + +Read the submitter. + +```js +form.addEventListener("submit", (event) => { + const intent = event.submitter?.value; +}); +``` + +Use `formaction`, `formmethod`, `formenctype`, and `formtarget` for per-button behavior. + +```html +<button type="submit">Save</button> +<button type="submit" formaction="/preview" formtarget="_blank"> + Preview +</button> +``` + +Use `formnovalidate` for drafts or previews where incomplete data is allowed. + +```html +<button type="submit" name="intent" value="draft" formnovalidate> + Save draft +</button> +``` + +## Dynamic Forms + +When adding or removing fields, maintain: + +```text +name attributes +label associations +error associations +focus behavior +validation rules +serialization +server expectations +``` + +For repeated fields, use a consistent naming convention. + +```html +<input name="emails[]" type="email"> +<input name="emails[]" type="email"> +``` + +Or structured indexes: + +```html +<input name="contacts[0][email]" type="email"> +<input name="contacts[1][email]" type="email"> +``` + +Match server parsing expectations. + +After adding a new field, focus it if the user initiated the action. + +```js +newInput.focus(); +``` + +After removing a field, move focus to a logical nearby control. + +Do not unexpectedly remove user-entered data. + +Ask for confirmation before deleting complex unsaved sections. + +Use event delegation so dynamically added fields work automatically. + +## File Uploads + +Use file input. + +```html +<input type="file" name="documents" multiple> +``` + +Use `accept` as a user convenience. + +```html +<input type="file" accept="image/*"> +``` + +For camera capture on mobile, `capture` can hint at direct capture. + +```html +<input type="file" accept="image/*" capture="environment"> +``` + +Treat `capture` as progressive enhancement. + +Read selected files from `input.files`. + +```js +for (const file of input.files) { + console.log(file.name, file.size, file.type); +} +``` + +Use `FileReader` or object URLs for previews. + +```js +const url = URL.createObjectURL(file); +image.src = url; + +image.addEventListener("load", () => { + URL.revokeObjectURL(url); +}); +``` + +Validate file size client-side for UX. + +```js +const maxSize = 5 * 1024 * 1024; + +if (file.size > maxSize) { + input.setCustomValidity("Choose a file smaller than 5 MB."); +} +``` + +Still validate server-side. + +Use drag-and-drop as enhancement, not the only upload path. + +```js +dropZone.addEventListener("drop", (event) => { + event.preventDefault(); + input.files = event.dataTransfer.files; +}); +``` + +Assigning to `input.files` may be limited depending on browser/security context. Always keep the native file input usable. + +For upload progress, use `XMLHttpRequest` upload progress events or a fetch upload streaming approach where supported. Plain `fetch` historically lacks simple upload progress. + +```js +const xhr = new XMLHttpRequest(); + +xhr.upload.addEventListener("progress", (event) => { + if (!event.lengthComputable) return; + const percent = (event.loaded / event.total) * 100; +}); +``` + +## Dates and Times + +Use native date/time controls when they meet product needs. + +```html +<input type="date" name="startDate"> +<input type="time" name="startTime"> +<input type="datetime-local" name="startsAt"> +``` + +Understand submitted formats: + +```text +date: YYYY-MM-DD +time: HH:mm or HH:mm:ss +datetime-local: YYYY-MM-DDTHH:mm +month: YYYY-MM +week: YYYY-Www +``` + +Do not store ambiguous local date-times without knowing the intended timezone. + +For birthdays and calendar dates, a date without timezone is often correct. + +For moments in time, convert to an instant with timezone context. + +Use min/max for date constraints. + +```html +<input type="date" name="arrival" min="2026-01-01" max="2026-12-31"> +``` + +Avoid complex date regexes. + +Use date parsing libraries or server validation for complex calendars and time zones when necessary. + +## Numbers, Currency, and Measurement + +Use `type="number"` for real numeric quantities. + +```html +<input type="number" name="quantity" min="1" step="1"> +``` + +Do not use `type="number"` for: + +```text +credit cards +ZIP/postal codes +phone numbers +account numbers +IDs with leading zeroes +fixed-length numeric codes +``` + +Use `inputmode="decimal"` for decimal entry when storing a string or applying locale-aware parsing. + +```html +<input name="amount" inputmode="decimal"> +``` + +Currency entry is tricky because locale formatting differs. + +Prefer storing minor units server-side, such as cents, after reliable parsing. + +Use `Intl.NumberFormat` for display, not necessarily parsing. + +Avoid floating-point errors for money. Use integers for minor units or decimal libraries. + +## Search and Filter Forms + +Use `method="get"` for shareable URLs. + +```html +<form method="get" action="/products"> + <input type="search" name="q"> + <select name="sort"> + <option value="relevance">Relevance</option> + <option value="price">Price</option> + </select> + <button type="submit">Apply</button> +</form> +``` + +Preserve query state in inputs. + +Use empty values intentionally. + +Avoid submitting meaningless empty parameters when possible, though this can be handled server-side. + +For live search, debounce input and update results progressively. + +Keep a submit button for accessibility and non-JS fallback. + +Update URL query params for shareability. + +## Authentication Forms + +Use correct autocomplete. + +Login: + +```html +<input name="username" autocomplete="username"> +<input name="password" type="password" autocomplete="current-password"> +``` + +Signup/change password: + +```html +<input name="username" autocomplete="username"> +<input name="password" type="password" autocomplete="new-password"> +``` + +Allow paste into password fields. + +Do not impose arbitrary low maximum password lengths. + +Support password managers. + +Use clear password requirement messaging. + +Avoid composition rules that reduce security and usability, such as requiring one uppercase, one lowercase, one number, and one symbol while limiting length severely. + +Prefer minimum length and breached-password checks server-side. + +Provide show/hide password toggle as a real button. + +```html +<button type="button" aria-controls="password" aria-pressed="false"> + Show password +</button> +``` + +```js +button.addEventListener("click", () => { + const showing = password.type === "text"; + password.type = showing ? "password" : "text"; + button.setAttribute("aria-pressed", String(!showing)); + button.textContent = showing ? "Show password" : "Hide password"; +}); +``` + +Maintain focus when toggling password visibility. + +Use generic authentication error messages. + +Use rate limiting and account protection server-side. + +For MFA/OTP, use `autocomplete="one-time-code"`. + +```html +<input + name="code" + inputmode="numeric" + autocomplete="one-time-code" + maxlength="6" +/> +``` + +## Payment Forms + +Prefer payment provider-hosted fields for card data. + +Use autocomplete tokens for payment fields when implementing them. + +```html +<input name="cc-name" autocomplete="cc-name"> +<input name="cc-number" autocomplete="cc-number" inputmode="numeric"> +<input name="cc-exp" autocomplete="cc-exp"> +<input name="cc-csc" autocomplete="cc-csc" inputmode="numeric"> +``` + +Do not use `type="number"` for card numbers. + +Card numbers can be long, have leading digits that matter, and should not expose number input spinners. + +Format card numbers visually without changing the submitted canonical value unless handled carefully. + +Validate card details with provider/server validation. + +Do not store card data unless compliant with payment standards. + +Use the Payment Request API only as progressive enhancement where it fits and is supported. + +## Contact, Address, and Profile Forms + +Use appropriate autocomplete fields. + +```html +<input name="givenName" autocomplete="given-name"> +<input name="familyName" autocomplete="family-name"> +<input name="email" type="email" autocomplete="email"> +<input name="phone" type="tel" autocomplete="tel"> +<input name="street" autocomplete="street-address"> +<input name="city" autocomplete="address-level2"> +<input name="region" autocomplete="address-level1"> +<input name="postalCode" autocomplete="postal-code"> +<input name="country" autocomplete="country"> +``` + +Support international address differences. + +Do not assume state/province is required. + +Do not assume postal code exists. + +Let users enter organization, apartment, unit, or address line 2 where needed. + +For country selectors, use stable country codes as values. + +```html +<option value="US">United States</option> +<option value="CA">Canada</option> +``` + +## Mobile Forms + +Use appropriate input types and `inputmode`. + +Use visible labels and large enough controls. + +Avoid tiny inline links inside labels when they are hard to tap. + +Place related controls close together. + +Avoid layouts requiring horizontal scrolling. + +Use `autocomplete` to reduce typing. + +Use `enterkeyhint` to guide virtual keyboard actions. + +Avoid fixed-position footers that cover fields when the keyboard opens unless carefully handled. + +Test with viewport resizing and virtual keyboard behavior. + +Do not rely only on hover. + +## Progressive Enhancement + +Start with a working HTML form. + +```html +<form method="post" action="/subscribe"> + <label for="email">Email</label> + <input id="email" name="email" type="email" required> + <button type="submit">Subscribe</button> +</form> +``` + +Enhance with JS. + +```js +form.addEventListener("submit", async (event) => { + event.preventDefault(); + ... +}); +``` + +If JS fails, the form should still submit. + +Use feature detection. + +```js +if ("FormData" in window && "fetch" in window) { + enhanceForm(); +} +``` + +Use CSS feature queries. + +```css +@supports selector(:has(*)) { + ... +} +``` + +Use cutting-edge features only when optional. + +Examples of progressive features: + +```text +:has() +:user-valid / :user-invalid +popover for lightweight field help or custom pickers +showPicker() for supported date/color/file controls +form-associated custom elements +Payment Request API +View Transitions around form-driven navigation +``` + +Always provide a fallback. + +## Form-Associated Custom Elements + +For custom elements that need to participate in forms, use form-associated custom elements where supported. + +```js +class CustomRating extends HTMLElement { + static formAssociated = true; + + #internals = this.attachInternals(); + + set value(value) { + this.#internals.setFormValue(value); + } + + setValidity(flags, message, anchor) { + this.#internals.setValidity(flags, message, anchor); + } +} + +customElements.define("custom-rating", CustomRating); +``` + +This is advanced and should be used only when a real custom form control is necessary. + +Provide fallback hidden inputs if needed. + +## Popover and Dialog in Forms + +Use `<dialog>` for modal confirmation flows. + +```html +<dialog id="confirm-dialog"> + <form method="dialog"> + <p>Discard changes?</p> + <button value="cancel">Cancel</button> + <button value="confirm">Discard</button> + </form> +</dialog> +``` + +Use `showModal()` for modal dialogs. + +```js +dialog.showModal(); +``` + +Use the Popover API for lightweight non-modal UI where supported. + +```html +<button type="button" popovertarget="password-help">Password help</button> +<div id="password-help" popover> + Use at least 12 characters. +</div> +``` + +Treat popover as progressive enhancement if support or UX requirements demand fallback. + +Do not put essential error text only in a popover. + +## Dirty State and Unsaved Changes + +Track whether users changed a form. + +```js +let dirty = false; + +form.addEventListener("input", () => { + dirty = true; +}); +``` + +Warn before leaving only when there are meaningful unsaved changes. + +```js +window.addEventListener("beforeunload", (event) => { + if (!dirty) return; + event.preventDefault(); + event.returnValue = ""; +}); +``` + +Use sparingly. Browser UI is generic. + +Clear dirty state after successful submit. + +```js +dirty = false; +``` + +Consider autosave for long forms. + +Use local storage cautiously. + +Do not store sensitive fields in local storage. + +## Autosave + +Debounce autosave. + +```js +const autosave = debounce(async () => { + const formData = new FormData(form); + await fetch("/draft", { + method: "POST", + body: formData, + }); +}, 1000); + +form.addEventListener("input", autosave); +``` + +Show save status accessibly. + +```html +<p role="status" id="save-status"></p> +``` + +Handle conflicts and stale saves. + +Use server-generated revision IDs or timestamps. + +Do not autosave passwords, payment info, secrets, or highly sensitive fields. + +## Reset and Clear + +Use reset carefully. + +```html +<button type="reset">Reset</button> +``` + +Reset returns controls to initial values, not empty values. + +Confirm destructive resets for long forms. + +Listen for `reset` if UI state needs clearing. + +```js +form.addEventListener("reset", () => { + clearErrors(); +}); +``` + +Because reset occurs before values update in some patterns, defer when necessary. + +```js +form.addEventListener("reset", () => { + queueMicrotask(clearErrors); +}); +``` + +## Browser Events + +Useful form events: + +```text +input +change +submit +reset +invalid +formdata +focus +blur +focusin +focusout +beforeinput +compositionstart +compositionupdate +compositionend +``` + +Use `input` for immediate value changes. + +Use `change` for committed changes, especially selects, checkboxes, radios, and file inputs. + +Use `focusin` and `focusout` when you need bubbling focus events. + +Use composition events or avoid aggressive validation during IME composition. + +```js +let composing = false; + +input.addEventListener("compositionstart", () => { + composing = true; +}); + +input.addEventListener("compositionend", () => { + composing = false; + validate(); +}); + +input.addEventListener("input", () => { + if (!composing) validate(); +}); +``` + +Use `beforeinput` only for advanced editing behavior. + +Avoid blocking input unless absolutely necessary. Validate after entry instead. + +## Data Normalization + +Trim values where appropriate. + +```js +const email = String(formData.get("email") ?? "").trim(); +``` + +Do not blindly trim fields where whitespace may be meaningful. + +Normalize email cautiously. + +Usually trim and maybe lowercase the domain. Do not assume all local parts are case-insensitive, though many systems treat them that way. + +Normalize phone numbers server-side using a dedicated library when needed. + +Normalize Unicode where needed. + +```js +const normalized = value.normalize("NFC"); +``` + +Preserve original user-entered names and addresses for display unless normalization is required. + +Separate display formatting from stored canonical values. + +## Testing Forms + +Test native submission behavior. + +Test validation success and failure. + +Test required fields. + +Test keyboard navigation. + +Test screen reader labels and descriptions. + +Test autofill and password manager behavior where practical. + +Test mobile input keyboards. + +Test server-side validation errors rendering back into the form. + +Test repeated submissions. + +Test network failure. + +Test slow submissions. + +Test file upload limits. + +Test dynamic field add/remove behavior. + +Test international data: + +```text +accented names +single-word names +long names +non-Latin scripts +international phone numbers +postal codes with letters/spaces +right-to-left text +``` + +Test empty, malformed, boundary, and malicious input. + +Use automated tests for serialization and validation logic. + +Use browser tests for important user flows. + +Manual accessibility testing is still important. + +## Common Anti-Patterns + +Using placeholders as labels. + +Using `div` or `span` instead of real form controls. + +Using `onclick` on non-buttons for submit actions. + +Using `type="number"` for credit cards, ZIP codes, phone numbers, or IDs. + +Disabling paste in password or OTP fields. + +Hiding focus outlines. + +Relying only on color for errors. + +Showing vague error messages. + +Clearing the whole form after a validation error. + +Validating only on the client. + +Trusting hidden fields. + +Submitting sensitive data via GET. + +Breaking password managers with fake fields. + +Creating custom selects without keyboard support. + +Replacing native date input with an inaccessible date picker. + +Using `form.submit()` when `requestSubmit()` is intended. + +Forgetting `name` attributes. + +Forgetting `type="button"` on non-submit buttons. + +Using positive `tabindex`. + +Putting interactive elements inside labels in ways that create confusing activation behavior. + +Overusing live regions. + +Blocking IME input or non-English characters. + +Using regexes that reject valid real-world names, emails, addresses, or phone numbers. + +Making disabled text unreadable. + +Not handling duplicate submissions. + +Not preserving values after server errors. + +## Recommended Baseline Patterns + +A robust simple field: + +```html +<div class="field"> + <label for="email">Email</label> + <input + id="email" + name="email" + type="email" + autocomplete="email" + required + aria-describedby="email-error" + > + <p id="email-error" class="field-error"></p> +</div> +``` + +A robust form: + +```html +<form method="post" action="/account" novalidate> + <div class="field"> + <label for="display-name">Display name</label> + <input + id="display-name" + name="displayName" + autocomplete="name" + required + minlength="2" + aria-describedby="display-name-error" + > + <p id="display-name-error" class="field-error"></p> + </div> + + <div class="field"> + <label for="email">Email</label> + <input + id="email" + name="email" + type="email" + autocomplete="email" + required + aria-describedby="email-error" + > + <p id="email-error" class="field-error"></p> + </div> + + <p id="form-status" role="status"></p> + + <button type="submit">Save</button> +</form> +``` + +Basic validation enhancement: + +```js +const form = document.querySelector("form"); + +function setError(input, message) { + const error = document.getElementById(`${input.id}-error`); + + input.setCustomValidity(message); + input.toggleAttribute("aria-invalid", Boolean(message)); + + if (error) { + error.textContent = message; + } +} + +function validateField(input) { + setError(input, ""); + + if (input.validity.valueMissing) { + setError(input, "Complete this field."); + return false; + } + + if (input.validity.typeMismatch && input.type === "email") { + setError(input, "Enter a valid email address."); + return false; + } + + if (input.validity.tooShort) { + setError(input, `Use at least ${input.minLength} characters.`); + return false; + } + + return true; +} + +form.addEventListener("input", (event) => { + const input = event.target; + + if (!(input instanceof HTMLInputElement)) return; + if (!input.hasAttribute("aria-invalid")) return; + + validateField(input); +}); + +form.addEventListener("submit", async (event) => { + event.preventDefault(); + + const fields = form.querySelectorAll("input, select, textarea"); + const valid = [...fields].every(validateField); + + if (!valid || !form.reportValidity()) { + return; + } + + const submitter = event.submitter; + submitter.disabled = true; + + try { + const response = await fetch(form.action, { + method: form.method, + body: new FormData(form, submitter), + }); + + if (!response.ok) { + throw new Error("Submission failed"); + } + } finally { + submitter.disabled = false; + } +}); +``` + +A non-JS-friendly async form should keep its `action` and `method` valid. + +## Practical Defaults I Would Apply + +Use semantic HTML form controls. + +Use visible labels. + +Use native validation attributes for straightforward constraints. + +Use `FormData` for serialization. + +Use `requestSubmit()` for programmatic submission. + +Use `SubmitEvent.submitter` for multiple submit buttons. + +Use `autocomplete` tokens. + +Use `inputmode` for mobile keyboard hints. + +Use `type="text"` instead of `type="number"` for numeric-looking identifiers. + +Use `aria-describedby` for help and errors. + +Use `aria-invalid` only when invalid. + +Use server-side validation as authoritative. + +Use progressive enhancement for async submission. + +Use `AbortController` for canceling stale async work. + +Use `:focus-visible`, `:focus-within`, `accent-color`, and `color-scheme`. + +Use `:has()` when it simplifies styling and fallback is acceptable. + +Use `fieldset` and `legend` for grouped options. + +Use `button type="button"` for non-submit buttons. + +Use `method="get"` for searches and filters. + +Use `method="post"` for mutations. + +Use `multipart/form-data` for uploads. + +Use status regions for submit/save feedback. + +Use error summaries for long forms. + +Use real links for navigation and real buttons for actions. + +Use clean separation between validation, rendering, and submission logic. + +Preserve user input across errors. + +Avoid custom controls unless the product need outweighs the accessibility and maintenance cost. diff --git a/.agents/skills/project-discipline-guides/mirrors/forms/gemini_mirror.md b/.agents/skills/project-discipline-guides/mirrors/forms/gemini_mirror.md new file mode 100644 index 000000000..38754dedf --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/forms/gemini_mirror.md @@ -0,0 +1,115 @@ +# Forms Development: Standard Best Practices & APIs (Redundancy Mirror) + +This guide represents the baseline "Common Knowledge" for modern web forms development. It covers standard APIs, semantic HTML, modern CSS, and accessibility patterns that are considered default expectations for high-quality web applications. + +--- + +## 1. Semantic Foundation & HTML Structure + +The structure of a form should rely on native HTML elements to ensure accessibility and browser-native behaviors (like "Enter to submit"). + +### Core Elements +- **`<form>`**: Always wrap inputs in a form element. Use the `novalidate` attribute if you intend to handle all validation styling via JavaScript while still leveraging native constraints. +- **`<label>`**: Every input must have a label. Use the `for` attribute to link to the input's `id`. Nesting the input inside the label is an alternative but `for/id` is preferred for maximum compatibility. +- **`<fieldset>` and `<legend>`**: Use these to group related inputs (e.g., address components, radio groups) to provide context to assistive technologies. +- **`<button type="submit">`**: Always use an explicit `type="submit"` for the primary action. Use `type="button"` for other actions to prevent accidental submissions. +- **`<input type="...">`**: Use specific types to trigger appropriate mobile keyboards and built-in validation: + - `email`, `tel`, `url`, `number`, `date`, `time`, `search`, `color`. + +### Essential Attributes +- **`name`**: Required for data submission and `FormData` serialization. +- **`autocomplete`**: Crucial for UX. Use specific values like `email`, `current-password`, `new-password`, `street-address`, `cc-number`, etc. +- **`inputmode`**: Refines mobile keyboards (e.g., `numeric`, `decimal`, `tel`). +- **`enterkeyhint`**: Customizes the "Enter" key on virtual keyboards (e.g., `next`, `done`, `send`). + +--- + +## 2. Native Constraint Validation + +Browsers provide a robust validation engine without JavaScript. + +### Validation Attributes +- **`required`**: Ensures the field is not empty. +- **`minlength` / `maxlength`**: Constraints for text length. +- **`min` / `max` / `step`**: Constraints for numeric and date types. +- **`pattern`**: A Regular Expression for custom validation. +- **`typeMismatch`**: Handled automatically by types like `email` or `url`. + +--- + +## 3. JavaScript APIs + +Modern JavaScript provides powerful tools for interacting with form data and the validation engine. + +### Constraint Validation API +Accessible via `element.validity` and other methods: +- **`checkValidity()`**: Returns `true` if the element/form meets all constraints. +- **`reportValidity()`**: Like `checkValidity()`, but also triggers the browser's native error UI. +- **`setCustomValidity(message)`**: Sets a custom error message. Setting it to an empty string `""` marks the field as valid. +- **`validity` Object**: Inspect specific errors: + - `valueMissing`, `typeMismatch`, `patternMismatch`, `tooShort`, `tooLong`, `rangeUnderflow`, `rangeOverflow`, `stepMismatch`, `customError`, `valid`. + +### FormData API +Simplifies data collection for AJAX/Fetch: +```javascript +const form = document.querySelector('form'); +form.addEventListener('submit', (e) => { + e.preventDefault(); + const data = new FormData(form); + const values = Object.fromEntries(data.entries()); + // Send values via fetch... +}); +``` +- **`formdata` Event**: A modern event that fires when the `FormData` object is constructed, allowing for late-stage modifications. + +--- + +## 4. Modern CSS for Forms + +CSS has evolved to handle form states natively, reducing the need for "touched/dirty" state tracking in JS. + +### Pseudo-classes +- **`:required` and `:optional`**: Style based on necessity. +- **`:valid` and `:invalid`**: Immediate feedback (use with caution as they fire on page load). +- **`:user-valid` and `:user-invalid` (Baseline 2023/24)**: The "gold standard." Only applies after the user has interacted with the field and moved away, preventing "premature" error styling. +- **`:placeholder-shown`**: Useful for creating "floating labels" or hiding/showing labels based on input content. +- **`:focus-visible`**: Styles the focus ring only when appropriate (e.g., keyboard navigation). +- **`:disabled` and `:read-only`**: Style state-constrained fields. + +### Modern Properties +- **`accent-color`**: Quickly brand checkboxes, radio buttons, and range sliders. +- **`appearance: none`**: Resets native styling for full custom control. +- **`field-sizing: content` (Experimental/Chrome)**: Allows textareas to grow automatically with their content. + +--- + +## 5. Accessibility (A11y) + +Form accessibility is more than just labels; it's about context and error communication. + +- **`aria-describedby`**: Link inputs to hint text or error messages. +- **`aria-invalid="true"`**: Programmatically indicate an error state to screen readers. +- **`aria-live="polite"`**: Use on error message containers to announce dynamic errors. +- **`required` attribute**: Screen readers announce this automatically; use `aria-required="true"` only if not using the native attribute. +- **Focus Management**: On submission error, move focus to the first invalid field or an error summary. + +--- + +## 6. Clean Code & UX Principles + +- **Progressive Enhancement**: Ensure the form works with just HTML/CSS. Add JS for enhanced UX (e.g., inline validation, masking). +- **Debounced Validation**: If performing expensive validation (like checking a username via API), debounce the input event. +- **Input Masking**: Use standard patterns for formatting (phone numbers, credit cards) but ensure the underlying value remains clean or is cleaned before submission. +- **Clear Submit State**: Disable the submit button or provide a loading indicator to prevent double-submissions. +- **Error Messaging**: Errors should be specific ("Email must contain @") rather than generic ("Invalid input"). + +--- + +## 7. Cutting Edge & Progressive Enhancements + +- **Popover API**: Use for custom date pickers or tooltips without needing Z-index hacks or heavy JS libraries. +- **EditContext API**: For advanced rich-text or custom input editors (primarily for complex web apps). +- **CSS `has()`**: Style parent containers or labels based on the validity of the child input: + ```css + .form-group:has(:user-invalid) label { color: red; } + ``` diff --git a/.agents/skills/project-discipline-guides/mirrors/forms/mirror.md b/.agents/skills/project-discipline-guides/mirrors/forms/mirror.md new file mode 100644 index 000000000..186a150b6 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/forms/mirror.md @@ -0,0 +1,114 @@ +# Forms Development: Unified Lowest Common Denominator (LCD) Mirror + +This document represents the intersection of standard development practices for web forms as consistently evidenced across Gemini, Claude, and Codex Knowledge Mirrors. + +--- + +## 1. Semantic HTML Structure + +The following elements and patterns are the verified foundation for accessible and functional forms. + +* **`<form>`**: The mandatory container for form controls. Use the `novalidate` attribute when handling validation via the Constraint Validation API to disable default browser error UI. +* **`<label>`**: Every input must have an associated label. + * **Explicit Association**: Use the `for` attribute on the label to match the `id` of the input (consistently identified as the preferred method). + * **Implicit Association**: Nesting the input inside the label. +* **`<fieldset>` and `<legend>`**: Used to group related controls (e.g., radio groups or address sections) to provide necessary context. +* **`<button>` types**: + * `type="submit"`: Explicitly defined for the primary form submission action. + * `type="button"`: Used for other actions to prevent accidental submissions. +* **`<textarea>`**: Standard element for multi-line text input. + +--- + +## 2. Specific Input Types + +ONLY the following specific `<input type="...">` values were consistently listed or referenced across all sources for their ability to trigger specialized mobile keyboards or built-in validation: + +* `email` +* `tel` +* `url` +* `number` +* `date` +* `time` +* `search` +* `color` +* `range` +* `checkbox` +* `radio` + +--- + +## 3. Essential Attributes + +These attributes are critical for user experience, data serialization, and mobile optimization. + +* **`name`**: Required for the control's value to be included in form submission and `FormData`. +* **`autocomplete`**: Crucial for UX and autofill. Consistently supported tokens include: + * `email`, `current-password`, `new-password`, `street-address`, `cc-number`. +* **`inputmode`**: Hints to the browser which virtual keyboard to display. Consistently evidenced values: + * `numeric`, `decimal`. +* **`enterkeyhint`**: Customizes the label of the "Enter" key on virtual keyboards. Consistently evidenced values: + * `next`, `done`, `send`. + +--- + +## 4. Native Constraint Validation + +The following attributes provide a baseline for browser-native validation without requiring JavaScript: + +* **`required`**: Ensures the field is not empty. +* **`minlength` / `maxlength`**: Constraints for text character length. +* **`min` / `max` / `step`**: Constraints for numeric and date types. +* **`pattern`**: A Regular Expression for custom input validation. + +--- + +## 5. JavaScript APIs + +Modern JavaScript provides the following tools for interacting with form data and validation states. + +### Constraint Validation API +* **`checkValidity()`**: Returns `true` if the element/form meets all constraints. +* **`reportValidity()`**: Checks validity and triggers the browser's native error UI. +* **`setCustomValidity(message)`**: Sets a custom error message; an empty string `""` clears the error. +* **`validity` Object**: Used to inspect specific error states: + * `valueMissing`, `typeMismatch`, `patternMismatch`, `tooShort`, `tooLong`, `rangeUnderflow`, `rangeOverflow`, `stepMismatch`, `customError`, `valid`. + +### FormData API +* **`new FormData(form)`**: Collects all named form fields into a single object. +* **`Object.fromEntries(formData)`**: Converts a `FormData` object into a standard JavaScript object. +* **`formdata` Event**: Fires when the `FormData` object is constructed, allowing for late-stage data modification. + +--- + +## 6. CSS for Forms + +The following pseudo-classes and properties are the standard for styling form states and branding native controls. + +### Pseudo-classes +* **`:valid` and `:invalid`**: Apply styles based on the current validity of the input. +* **`:user-valid` and `:user-invalid`**: Apply styles only after a user has interacted with the field (avoiding premature errors on page load). + +### Properties +* **`accent-color`**: Styles native checkboxes, radio buttons, and range sliders with a specific brand color. +* **`appearance: none`**: Resets native browser styling to allow for custom control designs. + +--- + +## 7. Accessibility (A11y) + +The following attributes and patterns ensure forms are perceivable and operable by all users. + +* **`aria-describedby`**: Links an input to helper text or error messages. +* **`aria-invalid`**: Programmatically indicates an error state (`true`/`false`). +* **`aria-live="polite"`**: Used on error containers to announce dynamic updates to screen readers. +* **`aria-required="true"`**: Used to indicate a required field (often alongside the native `required` attribute). +* **Focus Management**: Upon a failed submission, focus should be moved to the first invalid field or an error summary. + +--- + +## 8. Core Best Practices + +* **Progressive Enhancement**: Ensure the form is functional with standard HTML/CSS before adding JavaScript enhancements. +* **Prevent Double Submissions**: Provide a clear submit state (e.g., disabling the submit button) to prevent duplicate data entry during processing. +* **Labels over Placeholders**: Never use a `placeholder` as a replacement for a `<label>`. diff --git a/.agents/skills/project-discipline-guides/mirrors/html/claude_mirror.md b/.agents/skills/project-discipline-guides/mirrors/html/claude_mirror.md new file mode 100644 index 000000000..d913c9202 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/html/claude_mirror.md @@ -0,0 +1,503 @@ +# HTML Best Practices, Syntax, and APIs — Common Knowledge Guide + +## 1. Document Structure & Boilerplate + +### Minimal valid HTML5 document +```html +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Page Title + + + + + +``` + +### Required / strongly recommended elements +- `` — must be the very first line; triggers standards mode. +- `` — always set `lang` (and `dir` when relevant) for accessibility, screen readers, hyphenation, and translation tools. +- `` — must be in the first 1024 bytes; place as the first child of ``. +- `` — required for responsive design on mobile. +- `` — required; unique per page; describes the page concisely (~50–60 chars). + +### Optional but conventional `<head>` content +- `<meta name="description" content="...">` — used for SEO snippets. +- `<link rel="canonical" href="...">` — canonicalize URL variants. +- `<link rel="icon" href="/favicon.ico">` and `<link rel="apple-touch-icon" href="...">` +- Open Graph / Twitter Card meta tags for social previews. +- `<meta name="theme-color" content="#...">` — affects browser UI. +- `<link rel="manifest" href="/manifest.webmanifest">` — for PWAs. + +## 2. Semantic HTML + +Use semantic elements over generic `<div>`/`<span>` whenever they convey meaning. + +### Sectioning & landmark elements +- `<header>` — introductory content for a page or section. +- `<nav>` — primary navigation links. +- `<main>` — primary content of the page; only one per page; should not be nested inside `<article>`, `<aside>`, `<header>`, `<footer>`, or `<nav>`. +- `<article>` — self-contained, independently distributable content (blog post, comment). +- `<section>` — thematic grouping; should typically have a heading. +- `<aside>` — tangentially related content (sidebar, pull quote). +- `<footer>` — footer for nearest sectioning ancestor. +- `<address>` — contact info for the nearest `<article>`/`<body>`. + +### Headings +- One `<h1>` per page (or per top-level sectioning context). +- Don't skip heading levels (`<h1>` → `<h3>` is wrong); they form an outline. +- Don't choose heading level for visual size — use CSS for that. + +### Text-level semantics +- `<strong>` (importance) vs `<b>` (stylistic offset, e.g., keywords). +- `<em>` (stress emphasis) vs `<i>` (alternate voice/mood, e.g., foreign term). +- `<mark>` — highlighted/marked text relevant to current context. +- `<small>` — side comments, fine print. +- `<code>`, `<kbd>`, `<samp>`, `<var>` — for code, keyboard input, sample output, variables. +- `<abbr title="...">` — abbreviations with expansion in `title`. +- `<cite>` — title of a referenced work. +- `<q>` — short inline quote (browsers add quote marks); `<blockquote cite="...">` for longer quotes. +- `<time datetime="2026-05-13">May 13, 2026</time>` — machine-readable time/date. +- `<dfn>` — defining instance of a term. +- `<s>` — no longer accurate or relevant; `<del>`/`<ins>` — removed/added in an edited document. +- `<ruby>`, `<rt>`, `<rp>` — East Asian ruby annotations. + +### Lists +- `<ul>`, `<ol>`, `<li>` — unordered/ordered lists. +- `<ol reversed start="N" type="A|a|I|i|1">` — attributes for ordered lists. +- `<dl>`/`<dt>`/`<dd>` — description lists (term/definition pairs). + +### Figures +```html +<figure> + <img src="chart.png" alt="Quarterly revenue chart"> + <figcaption>Q1 2026 revenue grew 12%.</figcaption> +</figure> +``` + +### Details / disclosure widget +```html +<details> + <summary>More info</summary> + <p>Hidden content revealed on toggle.</p> +</details> +``` +- Native disclosure widget; supports `open` attribute and `toggle` event. +- `name` attribute (newer) groups `<details>` to behave like an accordion (only one open at a time). + +### Dialog +```html +<dialog id="d"> + <form method="dialog"> + <p>Modal content</p> + <button>Close</button> + </form> +</dialog> +<script> + document.getElementById('d').showModal(); // or .show() for non-modal +</script> +``` +- Native modal/non-modal dialog. Supports `::backdrop` styling, focus trapping, `Esc` to close (modal), and `closedby` attribute (newer). +- Forms with `method="dialog"` close the dialog and return value via `returnValue`. + +## 3. Links & Navigation + +### Anchor element +```html +<a href="https://example.com" target="_blank" rel="noopener noreferrer">External</a> +<a href="/docs/page#section">Internal</a> +<a href="mailto:hello@example.com?subject=Hi">Email</a> +<a href="tel:+15551234567">Call</a> +<a href="#main-content">Skip to main</a> +``` + +- Always use `rel="noopener"` (and historically `noreferrer`) with `target="_blank"` to prevent reverse-tabnabbing — modern browsers default to `noopener` but include explicitly for safety. +- Link text should be descriptive — avoid "click here", "read more"; the link text alone should make sense out of context. +- `download` attribute hints to the browser to download rather than navigate; can specify filename. +- `ping` for tracking pings on activation. +- `referrerpolicy` controls Referer header on outbound navigation. +- Skip-to-content links improve keyboard navigation accessibility. + +## 4. Images & Media + +### `<img>` +```html +<img src="hero.jpg" alt="A description of the image" + width="800" height="600" + loading="lazy" decoding="async" fetchpriority="high"> +``` + +- **Always** include `alt`. Empty `alt=""` for decorative images (screen readers skip). +- Set `width` and `height` (or aspect-ratio CSS) to prevent layout shift (CLS). +- `loading="lazy"` — defer offscreen images. +- `decoding="async"` — non-blocking image decode. +- `fetchpriority="high|low|auto"` — prioritization hint. + +### Responsive images +```html +<img src="small.jpg" + srcset="small.jpg 480w, medium.jpg 1024w, large.jpg 2000w" + sizes="(max-width: 600px) 100vw, 50vw" + alt="..."> +``` + +### `<picture>` for art direction / format fallbacks +```html +<picture> + <source type="image/avif" srcset="img.avif"> + <source type="image/webp" srcset="img.webp"> + <img src="img.jpg" alt="..." width="800" height="600"> +</picture> +``` + +### `<video>` / `<audio>` +```html +<video controls preload="metadata" poster="poster.jpg" width="640" height="360" + playsinline muted> + <source src="video.webm" type="video/webm"> + <source src="video.mp4" type="video/mp4"> + <track kind="captions" src="captions.vtt" srclang="en" label="English" default> + Your browser does not support video. +</video> +``` +- Provide multiple `<source>` for format fallbacks. +- Always provide captions/subtitles via `<track>` for accessibility. +- `playsinline` for iOS inline playback; `muted` required for autoplay in most browsers. +- `preload="none|metadata|auto"` — controls preload strategy. + +## 5. Forms + +### Basic form structure +```html +<form action="/submit" method="post" enctype="multipart/form-data" novalidate> + <fieldset> + <legend>Account info</legend> + <label for="email">Email</label> + <input id="email" name="email" type="email" required autocomplete="email"> + </fieldset> + <button type="submit">Submit</button> +</form> +``` + +### Input types (use appropriate ones for mobile keyboards & validation) +`text`, `email`, `password`, `tel`, `url`, `search`, `number`, `range`, `date`, `time`, `datetime-local`, `month`, `week`, `color`, `file`, `checkbox`, `radio`, `hidden`, `submit`, `reset`, `button`. + +### Important attributes +- `required`, `disabled`, `readonly`, `multiple`. +- `min`, `max`, `step`, `minlength`, `maxlength`, `pattern`. +- `placeholder` — hint only; **never** a substitute for `<label>`. +- `autocomplete` — use specific tokens (`email`, `current-password`, `new-password`, `one-time-code`, `cc-number`, `street-address`, etc.) to enable browser/password-manager autofill. +- `inputmode` — controls on-screen keyboard (`numeric`, `decimal`, `tel`, `email`, `url`, `search`). +- `enterkeyhint` — virtual keyboard's Enter key label (`go`, `done`, `next`, `search`, `send`). +- `name` — required for form submission inclusion. +- `form` attribute — associate input with form by ID, even outside it. +- `formaction`, `formmethod`, `formenctype`, `formnovalidate`, `formtarget` on submit buttons override the form's. + +### Labels — always associate +```html +<label for="username">Username</label> +<input id="username" name="username"> +<!-- or wrapping --> +<label>Username <input name="username"></label> +``` + +### Other form controls +- `<select>` with `<option>`, `<optgroup>`. `multiple`, `size` for list select. +- `<textarea rows="..." cols="..." wrap="hard|soft">` +- `<datalist id="x">` paired with `<input list="x">` for autocomplete suggestions. +- `<output for="a b" name="result">` for form computation results. +- `<progress value="" max="">` and `<meter min max low high optimum value>` — progress and gauge. +- `<button type="button|submit|reset">` — **always** specify `type` to avoid accidental form submissions. + +### Validation +- Use built-in constraints (`required`, `pattern`, `min/max`, `type=email|url`). +- `:valid`, `:invalid`, `:user-invalid`, `:user-valid`, `:required`, `:optional` CSS pseudo-classes. +- Constraint Validation API: `element.checkValidity()`, `reportValidity()`, `setCustomValidity()`, `validity` property. + +## 6. Tables + +```html +<table> + <caption>Quarterly Revenue</caption> + <colgroup> + <col span="1" style="background:#f6f6f6"> + <col span="2"> + </colgroup> + <thead> + <tr><th scope="col">Quarter</th><th scope="col">Revenue</th><th scope="col">Growth</th></tr> + </thead> + <tbody> + <tr><th scope="row">Q1</th><td>$1M</td><td>12%</td></tr> + </tbody> + <tfoot> + <tr><th scope="row">Total</th><td>$4M</td><td>—</td></tr> + </tfoot> +</table> +``` + +- Use `<table>` for **tabular data only** — never for layout. +- Always use `<caption>` for tables. +- Use `scope="col|row"` on `<th>` for accessibility. +- Use `<thead>`, `<tbody>`, `<tfoot>` to structure rows semantically. +- Use `headers="id1 id2"` for complex tables to associate cells with headers explicitly. + +## 7. Accessibility (a11y) + +### Core principles +- Prefer native HTML elements over ARIA (`<button>` vs `<div role="button">`). +- "No ARIA is better than bad ARIA." +- Maintain a logical DOM order; don't rely on `tabindex` for ordering. +- Visible focus indicators are required (don't `outline: none` without a replacement). + +### ARIA basics +- `role="..."` — only when no semantic element exists. +- `aria-label`, `aria-labelledby`, `aria-describedby` — accessible names/descriptions. +- `aria-hidden="true"` — hide from assistive tech (don't apply to focusable elements). +- `aria-expanded`, `aria-controls`, `aria-current`, `aria-pressed`, `aria-selected`, `aria-checked` — widget state. +- `aria-live="polite|assertive"` and `role="status|alert"` — live region announcements. +- `aria-busy`, `aria-disabled`, `aria-invalid`, `aria-required` — state. +- Landmark roles: `banner`, `navigation`, `main`, `complementary`, `contentinfo`, `search`, `form` (largely covered by HTML5 sectioning elements). + +### Focus management +- `tabindex="0"` — include in tab order. +- `tabindex="-1"` — focusable programmatically but not in tab order. +- Avoid `tabindex` > 0 (overrides natural order, hard to maintain). +- Use `inert` attribute to make a subtree non-interactive and hidden from a11y tree (great for modal backgrounds). +- Use `:focus-visible` in CSS for keyboard-only focus rings. + +### Other a11y attributes +- `autofocus` — sparingly, only when expected. +- `lang` on subtree elements when language differs from page. +- `hidden` attribute — fully hides element (equivalent to `display: none`); also `hidden="until-found"` allows in-page search to reveal it. +- Provide text alternatives for all non-text content. +- Sufficient color contrast (WCAG AA: 4.5:1 normal text, 3:1 large text). +- Don't rely on color alone to convey information. +- Respect `prefers-reduced-motion` for animations. + +## 8. Scripts & Stylesheets + +### Loading order +```html +<link rel="stylesheet" href="styles.css"> +<script src="app.js" defer></script> +<script src="module.js" type="module"></script> +``` + +- `defer` — download in parallel, execute in order after HTML parsing, before `DOMContentLoaded`. +- `async` — download in parallel, execute as soon as available (order not guaranteed). +- `type="module"` — implicitly deferred, supports `import`/`export`, runs in strict mode. +- `nomodule` — fallback for legacy browsers. +- Place `<script>` in `<head>` with `defer`/`module`, or at end of `<body>` for legacy scripts. + +### Resource hints +- `<link rel="preload" as="font|script|style|image" href="..." crossorigin>` +- `<link rel="prefetch" href="...">` — low-priority fetch for future navigation. +- `<link rel="preconnect" href="https://...">` — early DNS/TCP/TLS handshake. +- `<link rel="dns-prefetch" href="https://...">` — DNS only. +- `<link rel="modulepreload" href="...">` — for ES modules. + +### CSP & security +- `<meta http-equiv="Content-Security-Policy" content="...">` (prefer HTTP header). +- `integrity="sha384-..."` and `crossorigin` on `<script>`/`<link>` for SRI on third-party assets. + +## 9. Embedded Content + +### `<iframe>` +```html +<iframe src="https://example.com" title="Description" + sandbox="allow-scripts allow-same-origin" + loading="lazy" referrerpolicy="no-referrer" + allow="camera; microphone; fullscreen" + width="600" height="400"></iframe> +``` +- `title` is required for accessibility. +- Use `sandbox` to restrict iframe capabilities. +- `loading="lazy"` for offscreen iframes. +- `allow` for Permissions Policy. + +### `<canvas>` / `<svg>` / `<math>` — inline graphics +- `<canvas>` — bitmap drawing surface (2D, WebGL, WebGPU contexts via JS). +- `<svg>` — scalable vector graphics, fully part of DOM, styleable with CSS. +- `<math>` — MathML for mathematical notation. + +## 10. Microdata, Metadata & SEO + +- Use semantic HTML first; supplement with structured data (`<script type="application/ld+json">` is the modern preferred form). +- `<meta name="robots" content="index,follow|noindex|nofollow">` +- `<link rel="alternate" hreflang="es" href="...">` for internationalization. +- `<link rel="alternate" type="application/rss+xml" href="...">` for feeds. + +## 11. New / Modern Features (Shipping Across Browsers) + +### Popover API (shipped across all major browsers) +```html +<button popovertarget="menu">Menu</button> +<div id="menu" popover>Popover content</div> +``` +- Built-in light dismiss, focus management, top-layer rendering. +- `popover="auto"` (default; light-dismiss) vs `popover="manual"`. +- Pair with `::backdrop`, `:popover-open` pseudo-class. + +### Anchor positioning (newer; progressive enhancement) +- `anchor-name` and `position-anchor` CSS for tethering popovers/tooltips. + +### Lazy loading +- `loading="lazy"` on `<img>` and `<iframe>`. + +### `<input type="search">` — `search` events, clear button. + +### `inputmode` & `enterkeyhint` for mobile UX (shipped widely). + +### `decoding="async"` and `fetchpriority="high|low|auto"` on `<img>`/`<link>`/`<script>`. + +### `<dialog>` and `showModal()` (shipped everywhere). + +### `<details name="group">` — exclusive accordion behavior (newer, broadly shipped). + +### Customizable `<select>` (`<selectedcontent>`, in-page popover) — newer, progressive enhancement. + +## 12. Web Components / Custom Elements + +- Custom element names must contain a hyphen: `<my-component>`. +- `<template>` — inert HTML fragment, not rendered until cloned. +- `<slot>` — placeholder in shadow DOM for distributing children. +- Declarative shadow DOM with `<template shadowrootmode="open|closed">`. + +## 13. Common Clean-Code Principles + +### Markup quality +- Always close non-void tags. Self-close optional but consistent. +- Quote attribute values consistently (double quotes are conventional). +- Use lowercase element and attribute names. +- Use `kebab-case` for `data-*` attributes and `id`/`class` names. +- Use `data-*` attributes for app-specific data (`element.dataset.key`). +- Indent nested content for readability (2 spaces is common). +- Validate markup (W3C validator or build-time linters like html-validate). +- Prefer external CSS/JS over inline (separation of concerns). +- Minimize `<div>` soup — use semantic elements or restructure. +- Avoid deep nesting; flat structures are easier to style and reason about. + +### IDs and classes +- IDs must be unique per document. +- Classes are reusable; use BEM or similar conventions for clarity. +- Don't style on IDs (low specificity discipline); use classes for styling. +- `id` values can have any character but conventionally `kebab-case`. + +### Boolean attributes +- Presence implies true: `<input disabled>` or `<input disabled="">` — both equivalent. +- Don't write `disabled="false"` (still truthy). + +### Self-closing void elements +- `<img>`, `<br>`, `<hr>`, `<input>`, `<meta>`, `<link>`, `<source>`, `<track>`, `<wbr>`, `<col>`, `<area>`, `<base>`, `<embed>`, `<param>` (deprecated). +- Trailing slash optional in HTML5 (`<img />` valid but unnecessary). + +### Don't +- Don't use `<table>` for layout. +- Don't use `<br>` for spacing — use CSS margins. +- Don't use ` ` for indentation. +- Don't put block elements inside inline elements (some exceptions like `<a>` wrapping block in HTML5). +- Don't omit `alt` on `<img>`. +- Don't omit `<label>` association on form controls. +- Don't rely on `placeholder` as a label. +- Don't use `autoplay` for video/audio without `muted`. +- Don't use `<i>`/`<b>` purely for italic/bold styling — use `<em>`/`<strong>` for emphasis or CSS for styling. +- Don't use deprecated elements: `<font>`, `<center>`, `<marquee>`, `<frame>`, `<frameset>`, `<acronym>`, `<applet>`, `<big>`, `<tt>`, `<strike>`. +- Don't use deprecated presentational attributes (`align`, `bgcolor`, `border`, etc. — use CSS). + +### Character entities +- Use UTF-8 for the document so most characters can be written literally. +- Required entities: `&` (`&`), `<` (`<`), `>` (`>`), `"` (`"`), `'` (`'`). +- Use named or numeric entities for special chars (`©`, `—`, `…`). + +### Comments +```html +<!-- Comment --> +``` +- Don't nest `<!-- -->`; HTML comments don't nest. +- Don't include conditional comments (`<!--[if IE]>`) — IE is gone. + +## 14. Performance Best Practices + +- Set `width`/`height` on images and embeds to prevent layout shift. +- Use `loading="lazy"` for offscreen images/iframes. +- Use modern image formats (`AVIF`, `WebP`) with fallbacks. +- Use `srcset`/`sizes` for responsive images. +- Use `<link rel="preload">` for critical above-the-fold resources. +- Use `<link rel="preconnect">` for cross-origin fetches. +- Defer non-critical JS with `defer` or `async`. +- Inline critical CSS, defer the rest. +- Minimize DOM size and depth. +- Use `content-visibility: auto` (CSS) for offscreen sections. +- Use HTTP caching headers (server-side) and SRI for third-party assets. + +## 15. Internationalization + +- `<html lang="en">` is mandatory. +- Use `<html dir="rtl">` or `dir="auto"` on individual elements with mixed content. +- Use `lang` attribute on subtree elements when content language differs. +- Use `<bdi>` for user-generated text of unknown directionality (e.g., usernames). +- Use `<bdo dir="rtl">` to override directionality of a span. +- Use Unicode characters directly; avoid HTML entities for non-ASCII. +- Provide `hreflang` on alternate language links. + +## 16. Privacy & Security Attributes + +- `rel="noopener"` (default for `target="_blank"` in modern browsers). +- `rel="noreferrer"` to suppress Referer header. +- `referrerpolicy="no-referrer|same-origin|strict-origin-when-cross-origin|...">` on `<a>`, `<area>`, `<img>`, `<iframe>`, `<link>`, `<script>`. +- `crossorigin="anonymous|use-credentials"` for CORS-enabled resources. +- `integrity="sha384-..."` for Subresource Integrity on third-party scripts/styles. +- `sandbox="..."` on `<iframe>` to restrict capabilities. +- `allow="..."` on `<iframe>` for Permissions Policy. + +## 17. Progressive Enhancement + +- Build a baseline that works without JS, then enhance. +- Use `<noscript>` to provide a fallback message or alternative UI. +- Use feature detection (`@supports` in CSS, `'feature' in element` in JS) rather than user-agent sniffing. +- Provide fallbacks inside `<picture>`, `<video>`, `<audio>`, `<canvas>`. +- Use semantic forms that submit without JS, then enhance with `fetch()`. + +## 18. Common Patterns + +### Skip link +```html +<a class="skip-link" href="#main">Skip to main content</a> +<main id="main">...</main> +``` + +### Accessible icon button +```html +<button type="button" aria-label="Close"> + <svg aria-hidden="true" focusable="false">...</svg> +</button> +``` + +### External link +```html +<a href="https://example.com" target="_blank" rel="noopener"> + Example <span class="visually-hidden">(opens in new tab)</span> +</a> +``` + +### Visually hidden but accessible to screen readers +```html +<span class="visually-hidden">Description for screen readers</span> +``` +```css +.visually-hidden { + position: absolute; width: 1px; height: 1px; padding: 0; + margin: -1px; overflow: hidden; clip: rect(0,0,0,0); + white-space: nowrap; border: 0; +} +``` + +### Print-friendly +- Use `<link rel="stylesheet" media="print" href="print.css">` or `@media print`. + +--- + +This is the baseline I would assume any modern HTML developer knows and applies by default — anything in your project skill that overlaps this is a candidate for pruning. diff --git a/.agents/skills/project-discipline-guides/mirrors/html/codex_mirror.md b/.agents/skills/project-discipline-guides/mirrors/html/codex_mirror.md new file mode 100644 index 000000000..a40c61995 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/html/codex_mirror.md @@ -0,0 +1,1135 @@ +# HTML Redundancy Mirror: Common Knowledge Guide + +This is the HTML guidance I would treat as common, default knowledge when building modern web pages and applications. It reflects broadly shipped platform features and a few progressive-enhancement features that are reasonable to use with fallbacks. + +## Document Foundation + +Use standards mode with a minimal modern document shell: + +```html +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Meaningful Page Title + + +
+ +
+ + +``` + +Always include ``. + +Set the document language with `html[lang]`. + +Use UTF-8 with ``. + +Use the responsive viewport meta tag. + +Put meaningful, unique text in ``. + +Use valid nesting and close elements where required. + +Avoid obsolete presentational markup such as `<font>`, `<center>`, `bgcolor`, `border`, and layout tables. + +Prefer semantic HTML over generic `<div>` and `<span>` elements. + +Use lowercase tag and attribute names by convention. + +Quote attribute values consistently. + +Avoid duplicate `id` values. + +Use `class` for reusable styling hooks and `id` for unique anchors or relationships. + +Keep HTML responsible for structure and meaning, CSS for presentation, and JavaScript for behavior. + +## Semantic Structure + +Use landmark elements to describe page structure: + +```html +<header> +<nav aria-label="Primary"> +<main> +<section> +<article> +<aside> +<footer> +``` + +Use exactly one primary `<main>` per page or view. + +Use `<header>` and `<footer>` relative to the page or a sectioning element. + +Use `<article>` for self-contained content that could stand alone. + +Use `<section>` for thematically grouped content, usually with a heading. + +Use `<aside>` for tangential or complementary content. + +Use `<nav>` only for major navigation groups. + +Avoid wrapping everything in anonymous `<div>`s when a semantic element exists. + +Do not use headings for visual sizing only. + +Maintain logical heading order: + +```html +<h1>Page Topic</h1> +<h2>Section</h2> +<h3>Subsection</h3> +``` + +Use one clear page-level `<h1>` in typical documents. + +Do not skip heading levels merely for styling. + +Use lists for lists: + +```html +<ul> + <li>Item</li> +</ul> + +<ol> + <li>Step</li> +</ol> + +<dl> + <dt>Term</dt> + <dd>Description</dd> +</dl> +``` + +Use `<p>` for paragraphs, not line breaks. + +Use `<br>` only for meaningful line breaks, such as addresses or poetry. + +Use `<hr>` for thematic breaks, not decorative lines. + +Use `<blockquote>` for extended quotations and `<q>` for inline quotations where appropriate. + +Use `<cite>` for the title of a cited work, not the quoted person’s name unless that is the cited work. + +Use `<address>` for contact information related to the nearest article or page. + +## Text-Level Semantics + +Use semantic inline elements: + +```html +<strong>important</strong> +<em>emphasized</em> +<small>side comment</small> +<mark>highlighted</mark> +<code>inlineCode()</code> +<kbd>Ctrl</kbd> +<samp>program output</samp> +<var>x</var> +<abbr title="HyperText Markup Language">HTML</abbr> +<time datetime="2026-05-13">May 13, 2026</time> +``` + +Use `<strong>` for importance, not just bold styling. + +Use `<em>` for stress emphasis, not just italics. + +Use `<b>` and `<i>` only when their non-emphasis HTML meanings fit, such as keywords, product names, idiomatic terms, or alternate voice. + +Use `<s>` for no-longer-accurate text. + +Use `<del>` and `<ins>` for document edits. + +Use `<sub>` and `<sup>` only for true subscript/superscript semantics. + +Use machine-readable dates with `<time datetime="">`. + +## Links + +Use real links for navigation: + +```html +<a href="/account">Account</a> +``` + +Use buttons for actions: + +```html +<button type="button">Save</button> +``` + +Do not use `<a href="#">` as a button. + +Do not use clickable `<div>`s when `<button>` or `<a>` is correct. + +Use descriptive link text. Avoid “click here”. + +Use same-page anchors with valid target IDs: + +```html +<a href="#settings">Settings</a> +<section id="settings">...</section> +``` + +For external links that open a new tab: + +```html +<a href="https://example.com" target="_blank" rel="noopener noreferrer"> + External site +</a> +``` + +Prefer not to force new tabs unless there is a strong product reason. + +Use `download` only for same-origin or intentionally downloadable resources. + +Use `mailto:` and `tel:` links where appropriate. + +## Images and Media + +Use images with meaningful `alt` text: + +```html +<img src="/chart.png" alt="Quarterly revenue increased from $2M to $3M"> +``` + +Use empty alt text for decorative images: + +```html +<img src="/ornament.svg" alt=""> +``` + +Do not repeat nearby captions verbatim in `alt`. + +Use `<figure>` and `<figcaption>` for captioned media: + +```html +<figure> + <img src="/diagram.png" alt="System architecture diagram"> + <figcaption>Data flow between services.</figcaption> +</figure> +``` + +Set image dimensions to reduce layout shift: + +```html +<img src="/photo.jpg" width="800" height="600" alt="..."> +``` + +Use responsive images: + +```html +<img + src="small.jpg" + srcset="small.jpg 480w, medium.jpg 960w, large.jpg 1440w" + sizes="(max-width: 600px) 100vw, 600px" + alt="..."> +``` + +Use `<picture>` for art direction or format selection: + +```html +<picture> + <source srcset="image.avif" type="image/avif"> + <source srcset="image.webp" type="image/webp"> + <img src="image.jpg" alt="..."> +</picture> +``` + +Use lazy loading for non-critical below-the-fold images: + +```html +<img src="/gallery.jpg" loading="lazy" alt="..."> +``` + +Use `decoding="async"` for images that do not need synchronous decoding. + +Use `fetchpriority="high"` sparingly for the likely LCP image. + +Use SVG directly when it needs styling, scripting, or accessibility integration. + +Use external SVG/image files for cacheable static assets. + +For icons, ensure accessible names when interactive and hide decorative icons from assistive tech. + +Use `<video>` and `<audio>` with controls unless custom controls are fully accessible: + +```html +<video controls width="640" poster="/poster.jpg"> + <source src="/movie.webm" type="video/webm"> + <source src="/movie.mp4" type="video/mp4"> + <track kind="captions" src="/captions.vtt" srclang="en" label="English"> +</video> +``` + +Provide captions for meaningful video with audio. + +Avoid autoplaying media with sound. + +Use `muted playsinline` for mobile-friendly silent autoplay where appropriate. + +## Forms + +Use native form controls first. + +Associate every input with a label: + +```html +<label for="email">Email</label> +<input id="email" name="email" type="email" autocomplete="email"> +``` + +Implicit labels are also valid: + +```html +<label> + Email + <input name="email" type="email"> +</label> +``` + +Use correct input types: + +```html +<input type="text"> +<input type="email"> +<input type="url"> +<input type="tel"> +<input type="search"> +<input type="password"> +<input type="number"> +<input type="date"> +<input type="time"> +<input type="datetime-local"> +<input type="month"> +<input type="week"> +<input type="color"> +<input type="file"> +<input type="checkbox"> +<input type="radio"> +<input type="range"> +``` + +Use `textarea` for multiline text. + +Use `select` for constrained choices when native dropdown behavior is acceptable. + +Use radio buttons for one-of-many choices. + +Use checkboxes for independent boolean choices. + +Group related controls with `<fieldset>` and `<legend>`: + +```html +<fieldset> + <legend>Notification preferences</legend> + <label><input type="checkbox" name="email_updates"> Email updates</label> +</fieldset> +``` + +Use `name` attributes for submitted form data. + +Use `value` intentionally, especially for radios, checkboxes, and submit buttons. + +Use `button`, not clickable text, for form actions: + +```html +<button type="submit">Create account</button> +<button type="button">Cancel</button> +<button type="reset">Reset</button> +``` + +Set button `type` explicitly, especially inside forms. + +Use native validation attributes: + +```html +<input required minlength="8" maxlength="64"> +<input type="email" required> +<input type="number" min="1" max="10" step="1"> +<input pattern="[0-9]{5}" inputmode="numeric"> +``` + +Use `autocomplete` tokens for better UX and password manager support: + +```html +<input autocomplete="name"> +<input autocomplete="email"> +<input autocomplete="username"> +<input autocomplete="current-password"> +<input autocomplete="new-password"> +<input autocomplete="one-time-code"> +<input autocomplete="street-address"> +<input autocomplete="postal-code"> +``` + +Use `inputmode` to hint mobile keyboards. + +Use `enterkeyhint` to hint virtual keyboard action labels. + +Use `placeholder` only as an example or hint, not as a replacement for labels. + +Use `aria-describedby` to connect help or error text: + +```html +<label for="password">Password</label> +<input id="password" aria-describedby="password-help"> +<p id="password-help">Use at least 12 characters.</p> +``` + +Expose validation errors clearly in text near the field. + +Use `disabled` when a control should not be interactive or submitted. + +Use `readonly` when a value should be visible and submitted but not editable. + +Use `hidden` inputs only for non-sensitive data; never trust hidden input values on the server. + +Use `form`, `formaction`, `formenctype`, `formmethod`, `formnovalidate`, and `formtarget` when a submit button needs to override form behavior. + +Use `enctype="multipart/form-data"` for file uploads. + +Use `accept` and `multiple` on file inputs where appropriate. + +Use `capture` cautiously for camera/microphone capture hints on mobile. + +## Tables + +Use tables for tabular data, not layout. + +Use proper table structure: + +```html +<table> + <caption>Monthly revenue</caption> + <thead> + <tr> + <th scope="col">Month</th> + <th scope="col">Revenue</th> + </tr> + </thead> + <tbody> + <tr> + <th scope="row">January</th> + <td>$10,000</td> + </tr> + </tbody> +</table> +``` + +Use `<caption>` for table title or summary. + +Use `<th>` for headers and `scope` for simple relationships. + +Use `headers` and `id` for complex tables when needed. + +Use `<thead>`, `<tbody>`, and `<tfoot>` to clarify structure. + +Avoid empty cells when a clear value such as “Not applicable” is better. + +## Accessibility Defaults + +Prefer native semantic elements because they carry built-in roles, states, keyboard behavior, and accessibility mappings. + +Use ARIA only when native HTML cannot express the semantics. + +Do not add redundant roles: + +```html +<!-- unnecessary --> +<button role="button"> +``` + +Do not override native semantics incorrectly. + +Ensure all interactive elements are keyboard accessible. + +Preserve visible focus indicators. + +Use logical DOM order that matches reading and tab order. + +Avoid positive `tabindex` values. + +Use `tabindex="0"` only to add normally non-focusable custom elements into the tab order when truly needed. + +Use `tabindex="-1"` for programmatic focus targets. + +Use `aria-label`, `aria-labelledby`, or visible text to provide accessible names for controls. + +Prefer visible labels over invisible labels. + +Use `aria-hidden="true"` only for content that should be ignored by assistive technology. + +Never put focusable content inside an `aria-hidden` subtree. + +Use `hidden`, `display: none`, or `inert` for content that should not be reachable. + +Use `inert` for disabled page regions, such as background content behind a modal dialog. + +Use live regions sparingly: + +```html +<div aria-live="polite"></div> +<div role="alert"></div> +``` + +Use `aria-current="page"` for the current navigation item. + +Use `aria-expanded` and `aria-controls` for disclosure buttons. + +Use `aria-pressed` for toggle buttons. + +Use `aria-selected` only in widgets where that state is appropriate, such as tabs or listboxes. + +Use `aria-invalid` for invalid form fields when errors are shown. + +Use sufficient text alternatives for images, icons, media, and controls. + +Do not rely on color alone to convey meaning. + +Ensure text content is selectable and zoomable. + +Use skip links for pages with repeated navigation: + +```html +<a href="#main" class="skip-link">Skip to content</a> +<main id="main">...</main> +``` + +## Dialogs, Popovers, and Disclosure + +Use native `<dialog>` for modal or non-modal dialogs where appropriate: + +```html +<dialog id="confirm-dialog"> + <form method="dialog"> + <p>Delete this item?</p> + <button value="cancel">Cancel</button> + <button value="confirm">Delete</button> + </form> +</dialog> +``` + +Use `showModal()` for modal dialogs and `close()` to close. + +Ensure dialogs have a clear accessible name. + +Return focus sensibly after closing dialogs. + +Use `method="dialog"` for simple dialog form submission. + +Use the Popover API as progressive enhancement for lightweight overlays where supported: + +```html +<button popovertarget="menu">Menu</button> +<div id="menu" popover> + ... +</div> +``` + +Use popovers for menus, teaching UI, lightweight panels, and non-modal overlays, not for complex modal workflows. + +Use `<details>` and `<summary>` for native disclosure widgets: + +```html +<details> + <summary>Advanced settings</summary> + ... +</details> +``` + +Keep `<summary>` concise and descriptive. + +Do not use `<details>` for accordions if you need strict one-open-at-a-time behavior without additional scripting. + +## Metadata, SEO, and Social Sharing + +Use meaningful document titles. + +Use a concise meta description when the page is indexable: + +```html +<meta name="description" content="..."> +``` + +Use canonical URLs where duplicate URLs may exist: + +```html +<link rel="canonical" href="https://example.com/page"> +``` + +Use robots metadata intentionally: + +```html +<meta name="robots" content="noindex"> +``` + +Use Open Graph metadata for rich sharing: + +```html +<meta property="og:title" content="..."> +<meta property="og:description" content="..."> +<meta property="og:image" content="https://example.com/image.jpg"> +<meta property="og:url" content="https://example.com/page"> +<meta property="og:type" content="website"> +``` + +Use Twitter/X card metadata when needed: + +```html +<meta name="twitter:card" content="summary_large_image"> +``` + +Use structured data such as JSON-LD when it accurately represents page content: + +```html +<script type="application/ld+json"> +{ + "@context": "https://schema.org", + "@type": "Article", + "headline": "..." +} +</script> +``` + +Do not add misleading structured data. + +Set favicons and app icons: + +```html +<link rel="icon" href="/favicon.ico"> +<link rel="icon" href="/icon.svg" type="image/svg+xml"> +<link rel="apple-touch-icon" href="/apple-touch-icon.png"> +``` + +Use `theme-color` where appropriate: + +```html +<meta name="theme-color" content="#ffffff"> +``` + +## Scripts + +Prefer external scripts for maintainability and caching: + +```html +<script src="/app.js" type="module"></script> +``` + +Use `type="module"` for modern JavaScript. Module scripts are deferred by default. + +Use `defer` for classic scripts that should run after parsing: + +```html +<script src="/legacy.js" defer></script> +``` + +Use `async` only for independent scripts where execution order does not matter. + +Place critical blocking scripts only when absolutely necessary. + +Avoid inline event handlers: + +```html +<!-- avoid --> +<button onclick="save()"> +``` + +Prefer unobtrusive JavaScript: + +```js +document.querySelector("button").addEventListener("click", save); +``` + +Use `nomodule` only when serving legacy fallbacks: + +```html +<script type="module" src="/modern.js"></script> +<script nomodule src="/legacy.js"></script> +``` + +Use import maps as progressive enhancement where appropriate: + +```html +<script type="importmap"> +{ + "imports": { + "lib": "/vendor/lib.js" + } +} +</script> +``` + +Use Subresource Integrity for third-party scripts and styles: + +```html +<script + src="https://cdn.example.com/lib.js" + integrity="sha384-..." + crossorigin="anonymous"></script> +``` + +Avoid third-party scripts unless necessary. + +Load analytics, ads, and embeds with care because they affect performance, privacy, and security. + +## Stylesheets and CSS Hooks + +Use external stylesheets: + +```html +<link rel="stylesheet" href="/styles.css"> +``` + +Keep class names meaningful and stable. + +Avoid styling by fragile DOM depth selectors when reusable classes would be clearer. + +Use `data-*` attributes for private application metadata: + +```html +<button data-action="delete" data-id="123">Delete</button> +``` + +Do not encode large or sensitive data in HTML attributes. + +Use custom elements only when component encapsulation or interoperability justifies them. + +Use `part` and `exportparts` when exposing shadow DOM styling hooks. + +Use `slot` for declarative composition in web components. + +## Resource Loading and Performance + +Keep HTML small and meaningful. + +Prioritize critical content in the initial document. + +Avoid excessive DOM size and deeply nested structures. + +Use preload for critical assets discovered too late: + +```html +<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin> +<link rel="preload" href="/hero.avif" as="image"> +``` + +Use preconnect for critical cross-origin connections: + +```html +<link rel="preconnect" href="https://fonts.example.com" crossorigin> +``` + +Use DNS prefetch for lower-priority early resolution: + +```html +<link rel="dns-prefetch" href="//cdn.example.com"> +``` + +Use modulepreload for important module graphs: + +```html +<link rel="modulepreload" href="/app.js"> +``` + +Do not overuse preload; it can compete with more important resources. + +Use native lazy loading for below-the-fold images and iframes: + +```html +<iframe src="..." loading="lazy"></iframe> +``` + +Set dimensions or aspect-ratio for images, videos, iframes, and embeds to reduce layout shift. + +Use modern image formats such as AVIF and WebP with fallbacks. + +Compress and cache static assets. + +Avoid render-blocking resources where possible. + +Inline only truly critical CSS, and avoid large inline payloads. + +Avoid large hydration payloads when static HTML would work. + +Prefer server-rendered or static semantic HTML for content-heavy pages. + +## Security + +Escape untrusted text before inserting it into HTML. + +Do not concatenate untrusted strings into HTML. + +Avoid `innerHTML` with untrusted content. + +Use DOM APIs such as `textContent`, `setAttribute`, and element creation for untrusted values. + +Sanitize trusted-rich HTML with a well-maintained sanitizer. + +Use Content Security Policy: + +```html +<meta http-equiv="Content-Security-Policy" content="default-src 'self'"> +``` + +Prefer HTTP headers for CSP over meta tags when possible. + +Avoid inline scripts and styles when using strict CSP. + +Use nonces or hashes for necessary inline scripts. + +Use `rel="noopener noreferrer"` for untrusted `target="_blank"` links. + +Use sandboxed iframes for untrusted embedded content: + +```html +<iframe src="..." sandbox></iframe> +``` + +Add only the sandbox permissions actually needed. + +Use `allow` on iframes to grant specific features: + +```html +<iframe src="..." allow="fullscreen; clipboard-write"></iframe> +``` + +Never put secrets in HTML, data attributes, comments, or hidden inputs. + +Treat client-side validation as UX only; validate again on the server. + +Use HTTPS for production. + +Avoid mixed content. + +## Iframes and Embeds + +Use `<iframe>` only when isolation or external embedding is required. + +Always provide a `title` for iframes: + +```html +<iframe title="Map of office location" src="..."></iframe> +``` + +Use `loading="lazy"` for non-critical iframes. + +Set explicit dimensions or responsive containers. + +Use `sandbox` for untrusted content. + +Use `allowfullscreen` or `allow="fullscreen"` where needed. + +Avoid embedding heavy third-party widgets on critical pages unless necessary. + +## Internationalization + +Set the correct document language. + +Use `lang` on specific passages in another language: + +```html +<p lang="fr">Bonjour</p> +``` + +Use `dir="rtl"` for right-to-left content where needed. + +Prefer logical document order over visual order. + +Use Unicode text directly with UTF-8. + +Use `<bdi>` for isolated bidirectional text such as usernames: + +```html +<bdi>username</bdi> +``` + +Use `<bdo>` only when intentionally overriding text direction. + +Use locale-appropriate date, number, and currency formatting in displayed content. + +Avoid hardcoded assumptions about name order, address shape, phone format, and text length. + +## Custom Data and Declarative Hooks + +Use `data-*` attributes for simple, non-sensitive metadata: + +```html +<li data-user-id="42"></li> +``` + +Access via `element.dataset`. + +Use ARIA attributes for accessibility state, not as generic state storage. + +Use classes for styling state when CSS needs them. + +Use custom attributes only when defining custom elements or when there is a clear convention. + +## Web Components + +Use custom elements for reusable browser-native components: + +```js +customElements.define("user-card", class extends HTMLElement { + connectedCallback() { + this.textContent = "User"; + } +}); +``` + +Use hyphenated names for custom elements. + +Use shadow DOM for encapsulation when appropriate. + +Use slots for light DOM composition: + +```html +<template id="card-template"> + <article> + <slot name="title"></slot> + <slot></slot> + </article> +</template> +``` + +Use `<template>` for inert reusable markup. + +Use declarative shadow DOM as progressive enhancement where server-rendered shadow roots are useful: + +```html +<template shadowrootmode="open"> + ... +</template> +``` + +Provide fallbacks or light DOM content when needed. + +Do not use web components where a simple semantic element is enough. + +## Progressive Enhancement + +Start with functional HTML. + +Layer CSS for presentation. + +Layer JavaScript for richer behavior. + +Ensure core content and critical actions remain understandable when JavaScript fails, unless the product is inherently JavaScript-only. + +Use feature detection instead of browser sniffing. + +Use cutting-edge features when unsupported browsers still get a usable fallback. + +Good progressive-enhancement candidates include: + +```html +<dialog> +<div popover> +<details> +<input type="date"> +<input type="color"> +loading="lazy" +fetchpriority +inert +``` + +For new form controls, expect inconsistent UI and provide validation or alternate input paths when necessary. + +## Clean Code Principles for HTML + +Make the DOM reflect the content model. + +Use the simplest element that accurately describes the content or control. + +Prefer native behavior over custom reimplementation. + +Keep markup readable and consistently indented. + +Avoid excessive wrapper elements. + +Avoid deeply nested layouts. + +Avoid mixing unrelated responsibilities in one element. + +Avoid magic data attributes without clear naming. + +Use consistent naming conventions for classes, IDs, and data attributes. + +Keep components small enough that their structure is understandable. + +Remove dead markup, unused attributes, and commented-out blocks. + +Do not duplicate large chunks of markup if a template, component, or server partial is available. + +Do not prematurely abstract one-off markup. + +Make repeated structures consistent. + +Keep accessible names, visible labels, and test selectors stable. + +Prefer content-driven markup over layout-driven markup. + +Do not encode visual order in a way that breaks reading order. + +Validate assumptions with browser dev tools, accessibility tooling, and HTML validators when appropriate. + +## Common HTML APIs and DOM Usage + +Use DOM selection APIs: + +```js +document.querySelector(".item"); +document.querySelectorAll(".item"); +document.getElementById("main"); +``` + +Use element creation APIs: + +```js +const button = document.createElement("button"); +button.type = "button"; +button.textContent = "Save"; +``` + +Use `textContent` for text. + +Use `classList` for classes: + +```js +element.classList.add("is-active"); +element.classList.toggle("is-open", open); +``` + +Use `dataset` for `data-*` values: + +```js +const id = element.dataset.id; +``` + +Use event listeners: + +```js +element.addEventListener("click", event => {}); +``` + +Use event delegation for repeated dynamic elements: + +```js +list.addEventListener("click", event => { + const button = event.target.closest("button[data-action]"); + if (!button) return; +}); +``` + +Use `closest()` for ancestor matching. + +Use `matches()` for selector checks. + +Use `append`, `prepend`, `before`, `after`, `replaceWith`, and `remove`. + +Use `template.content.cloneNode(true)` for template cloning. + +Use `DocumentFragment` for batching DOM creation when useful. + +Use `hidden` for simple visibility state that should also affect accessibility tree exposure. + +Use `focus()` intentionally after navigation, modal open/close, validation errors, or dynamic content changes. + +Use `requestSubmit()` to submit forms through normal validation and submit handling. + +Use `FormData` for form serialization: + +```js +const data = new FormData(form); +``` + +Use the Constraint Validation API where useful: + +```js +input.validity.valid; +input.setCustomValidity("Custom error"); +form.reportValidity(); +``` + +Use `URL` and `URLSearchParams` for URL manipulation. + +Use `MutationObserver`, `ResizeObserver`, and `IntersectionObserver` when observation is genuinely needed. + +Use `IntersectionObserver` for visibility-triggered behavior rather than scroll polling. + +## Validation and Testing Expectations + +Run HTML through validation for content-heavy or reusable templates. + +Check keyboard navigation. + +Check screen reader names and roles with accessibility inspection tools. + +Check responsive behavior at common viewport widths. + +Check that images have appropriate alt text. + +Check that forms are labeled and errors are announced or discoverable. + +Check that links go somewhere and buttons do something. + +Check that no interactive control is unreachable by keyboard. + +Check that dialogs trap or manage focus appropriately when modal. + +Check that page title, landmarks, headings, and language are correct. + +Check performance basics: image dimensions, lazy loading, blocking resources, and third-party payloads. + +## Default Anti-Patterns + +Avoid layout tables. + +Avoid clickable non-interactive elements. + +Avoid missing labels on inputs. + +Avoid placeholder-only form labels. + +Avoid empty links and buttons. + +Avoid `href="javascript:void(0)"`. + +Avoid positive `tabindex`. + +Avoid removing focus outlines without replacement. + +Avoid inaccessible custom selects, menus, tabs, and modals. + +Avoid unlabeled icon buttons. + +Avoid images of text when real text can be used. + +Avoid putting important text only in CSS background images. + +Avoid autoplaying audio or video with sound. + +Avoid massive DOM trees for simple interfaces. + +Avoid untrusted `innerHTML`. + +Avoid relying on client-side validation for security. + +Avoid excessive ARIA. + +Avoid presentational markup in HTML. + +Avoid forcing new tabs unnecessarily. + +Avoid using headings out of order for visual style. + +Avoid using `<section>` without a meaningful section concept. + +Avoid leaving generated or framework boilerplate titles and metadata in production. diff --git a/.agents/skills/project-discipline-guides/mirrors/html/gemini_mirror.md b/.agents/skills/project-discipline-guides/mirrors/html/gemini_mirror.md new file mode 100644 index 000000000..b5bf3863c --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/html/gemini_mirror.md @@ -0,0 +1,118 @@ +# HTML Standard Best Practices & Modern API Guide (Redundancy Mirror) + +This guide represents the baseline of "Common Knowledge" for modern HTML development (HTML5 and beyond). It covers semantic structure, performance, accessibility, and modern Web APIs. + +## 1. Document Structure & Metadata + +### Baseline Standards +- **DOCTYPE**: Always start with `<!DOCTYPE html>`. +- **Language**: Define the document language using `<html lang="en">` for accessibility and SEO. +- **Character Encoding**: Specify UTF-8 in the first 1024 bytes: `<meta charset="UTF-8">`. +- **Viewport**: Ensure mobile responsiveness: `<meta name="viewport" content="width=device-width, initial-scale=1">`. +- **Title**: Every page must have a unique, descriptive `<title>`. + +### Modern Meta & Resource Hints +- **Pre-connect/Pre-load**: Use `<link rel="preconnect">` for critical third-party origins (e.g., fonts, APIs) and `<link rel="preload">` for high-priority assets needed early in the page lifecycle. +- **Resource Hints**: Use `dns-prefetch`, `prefetch`, and `prerender` for speculative loading of future resources. + +## 2. Semantic HTML & Landmarks + +### Core Semantic Elements +- **Landmarks**: Use `<header>`, `<nav>`, `<main>`, `<footer>`, `<aside>`, and `<section>` to define page structure. +- **Sectioning**: Use `<h1>` through `<h6>` in a strict hierarchical order. Avoid skipping levels. +- **Meaningful Tags**: + - `<article>` for self-contained content. + - `<figure>` and `<figcaption>` for annotated images/media. + - `<time>` for dates and times with the `datetime` attribute. + - `<address>` for contact information for the nearest `<article>` or `<body>`. + +### Text Level Semantics +- Use `<strong>` and `<em>` for emphasis, rather than `<b>` and `<i>` (which are now used for stylistic offsets without implied importance). +- Use `<mark>` for highlighted text. +- Use `<abbr>` with `title` for abbreviations. +- Use `<code>`, `<kbd>`, `<samp>`, and `<var>` for technical content. + +## 3. Forms & User Input + +### Modern Input Types +- Leverage specialized types for better mobile keyboards and built-in validation: `email`, `tel`, `url`, `number`, `date`, `time`, `datetime-local`, `month`, `week`, `color`, `range`, `search`. + +### Validation & UX +- **Built-in Attributes**: `required`, `pattern` (regex), `min`, `max`, `step`, `minlength`, `maxlength`. +- **Labels**: Always associate labels using the `for` attribute matching the input `id`. +- **Placeholders**: Never use `placeholder` as a replacement for a label. +- **Autocomplete**: Use the `autocomplete` attribute to assist password managers and browser autofill (e.g., `current-password`, `new-password`, `cc-number`, `shipping address-line1`). +- **Inputmode**: Use `inputmode` (e.g., `numeric`, `decimal`, `tel`) to provide specific virtual keyboards. + +### Advanced Form Elements +- **`<datalist>`**: Provide "suggested" options for an `<input>`. +- **`<output>`**: Display the result of a calculation or user action. +- **`<progress>`** and **`<meter>`**: Represent task progress or scalar measurements. + +## 4. Media & Graphics + +### Images +- **Responsive Images**: Use `<picture>` with multiple `<source>` elements for different formats (WebP, AVIF) and `srcset` with `sizes` for different resolutions/viewports. +- **Lazy Loading**: Use `loading="lazy"` for all images below the fold. +- **Decoding**: Use `decoding="async"` to reduce main-thread impact of image decoding. +- **Alt Text**: Always provide `alt` attributes. Use `alt=""` for purely decorative images. + +### Video & Audio +- Provide multiple sources for compatibility (MP4, WebM). +- Use the `poster` attribute for videos. +- Use `playsinline` for mobile autoplay (where muted). +- Always include `<track>` for captions/subtitles (WebVTT). + +### SVG +- Prefer inline SVG for icons to reduce HTTP requests and allow CSS styling. +- Use `aria-hidden="true"` on SVGs that are decorative. +- Provide a `<title>` and `<desc>` inside the SVG for complex graphics. + +## 5. Web APIs & Modern Features + +### The `<dialog>` Element +- Use `<dialog>` for modals and popovers. +- Use `.showModal()` for backdrop-blocking modals and `.show()` for non-modal dialogs. +- Leverage the `::backdrop` CSS pseudo-element for styling. + +### Web Components (Native) +- **`<template>`**: Define HTML fragments that are not rendered until instantiated via JavaScript. +- **`<slot>`**: Placeholders inside a Shadow DOM that users can fill with their own markup. +- **Shadow DOM**: Encapsulate styles and markup for custom elements. + +### Advanced HTML Attributes +- **`hidden`**: Hide elements without using CSS (acts like `display: none`). +- **`contenteditable`**: Allow users to edit element content directly. +- **`spellcheck`**: Enable/disable browser spellchecking. +- **`draggble`**: Native drag-and-drop support. +- **`popover`**: (Recent) Use for non-modal overlays without JavaScript (using `popovertarget`). + +## 6. Accessibility (A11y) Best Practices + +### Fundamental Rules +- **Interactive Elements**: Use `<button>` for actions and `<a>` for navigation. Never use a `<div>` or `<span>` for an action unless strictly necessary (and then apply `role="button"` and `tabindex="0"`). +- **Focus Management**: Ensure a visible focus indicator (`:focus` or `:focus-visible`). +- **Aria Attributes**: + - `aria-label`: For elements without visible text. + - `aria-labelledby`: Link to another element for a description. + - `aria-describedby`: For supplementary information. + - `aria-expanded`, `aria-hidden`, `aria-current`, `aria-live` (for dynamic updates). +- **Skip Links**: Provide a "Skip to main content" link at the top of the page. + +## 7. Performance & Clean Code + +### Loading Strategy +- **Script Placement**: Use `async` or `defer` on `<script>` tags. Place `defer` scripts in the `<head>` to start downloading early but execute after parsing. +- **CSS Placement**: Always place `<link rel="stylesheet">` in the `<head>`. + +### Syntax & Style +- Use lowercase for all tags and attributes. +- Use double quotes for attribute values. +- Self-close void elements (e.g., `<br>`, `<img>`, `<hr>`) or use the shorter syntax (e.g., `<br />` is valid but `<br>` is standard). +- Order attributes consistently (e.g., `class`, `id`, `name`, `data-*`, `src`/`href`, `alt`). +- Minimize deep nesting; flatten the DOM tree where possible for better performance. + +## 8. Progressive Enhancement +- Ensure the core content is accessible without JavaScript. +- Use `<noscript>` for critical fallback messaging if JS is required for the app to function. +- Test against high-contrast modes and screen readers. diff --git a/.agents/skills/project-discipline-guides/mirrors/html/mirror.md b/.agents/skills/project-discipline-guides/mirrors/html/mirror.md new file mode 100644 index 000000000..8b0b73c05 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/html/mirror.md @@ -0,0 +1,118 @@ +update_topic{strategic_intent:Generating the Unified Lowest Common Denominator (LCD) Mirror for HTML best practices.,summary:I have analyzed the three source mirrors (Gemini, Claude, and Codex) to identify the intersection of HTML best practices. I strictly applied the inclusion and exclusion rules, ensuring that only concepts explicitly present in all three documents are included. Notable exclusions due to missing evidence in at least one mirror include: HTML lists (ul, ol, dl), tables, anchor element security (rel="noopener"), and specific meta tags like canonical or description. I am now presenting the unified LCD mirror organized by standard technical headers.,title:Generating HTML LCD Mirror}# HTML Lowest Common Denominator (LCD) Mirror + +This document represents the unified intersection of "Common Knowledge" for HTML development, derived from the consensus of multiple models. It contains only the best practices, syntax, and APIs explicitly supported across all source mirrors. + +## 1. Document Structure & Metadata + +### Mandatory Boilerplate +- **DOCTYPE**: Always start the document with `<!DOCTYPE html>` to trigger standards mode. +- **Language**: Define the primary language of the document using the `lang` attribute on the `<html>` tag (e.g., `<html lang="en">`). +- **Character Encoding**: Specify UTF-8 encoding using `<meta charset="UTF-8">` within the first 1024 bytes of the document. +- **Viewport**: Ensure mobile responsiveness with `<meta name="viewport" content="width=device-width, initial-scale=1">`. +- **Title**: Provide a unique, descriptive `<title>` for every page. + +### Resource Hints +- **Performance Optimization**: Use the following `<link>` relationship types to optimize resource loading: + - `preconnect`: Establish early connections to critical third-party origins. + - `preload`: Prioritize the download of high-priority assets needed early in the page lifecycle. + - `dns-prefetch`: Resolve domain names before they are requested. + - `prefetch`: Speculatively fetch resources for future navigations. + +## 2. Semantic HTML & Page Structure + +### Landmark & Sectioning Elements +Use specific tags to define the structural regions of a page: +- `<header>`: Introductory content for a page or section. +- `<nav>`: A section intended for navigation links. +- `<main>`: The primary content of the document (use exactly one per page). +- `<section>`: A thematic grouping of content, typically including a heading. +- `<article>`: Self-contained content that can stand independently. +- `<aside>`: Content tangentially related to the main content (e.g., sidebars). +- `<footer>`: The footer for the nearest sectioning ancestor or the page. + +### Content Semantics +- **Headings**: Use `<h1>` through `<h6>` in a strict hierarchical order. Do not skip levels for styling purposes. +- **Figures**: Use `<figure>` to wrap images or media, and `<figcaption>` to provide a caption. +- **Time**: Use the `<time>` element for dates and times, employing the `datetime` attribute for machine-readable values. +- **Address**: Use `<address>` for contact information related to the document or an `<article>`. + +## 3. Text-Level Semantics + +- **Emphasis**: Use `<strong>` for importance and `<em>` for stress emphasis. +- **Stylistic Offsets**: Use `<b>` and `<i>` for stylistic purposes without implied importance. +- **Highlighting**: Use `<mark>` for text highlighted for reference or notation. +- **Abbreviations**: Use `<abbr>` with a `title` attribute to define abbreviations. +- **Technical Text**: Use `<code>` for inline code, `<kbd>` for keyboard input, `<samp>` for sample output, and `<var>` for variables. + +## 4. Forms & User Input + +### Input Types & Attributes +Leverage specific `type` attributes for better mobile keyboards and built-in validation: +- **Types**: `email`, `tel`, `url`, `number`, `date`, `time`, `datetime-local`, `month`, `week`, `color`, `range`, `search`. +- **Validation**: Use the attributes `required`, `pattern`, `min`, `max`, `step`, `minlength`, and `maxlength`. + +### User Experience & Accessibility +- **Labels**: Always associate a `<label>` with its input using the `for` attribute matching the input `id`. +- **Placeholders**: Never use the `placeholder` attribute as a replacement for a visible label. +- **Autofill**: Use the `autocomplete` attribute to assist browser autofill (e.g., `email`, `current-password`, `new-password`). +- **Virtual Keyboards**: Use the `inputmode` attribute (e.g., `numeric`, `decimal`, `tel`) to specify the type of keyboard displayed on mobile devices. + +### Functional Elements +- **`<datalist>`**: Provide suggested options for an `<input>`. +- **`<output>`**: Display the results of a calculation or user action. +- **`<progress>` & `<meter>`**: Represent task progress or scalar measurements within a known range. + +## 5. Media & Graphics + +### Images +- **Alt Text**: Always provide an `alt` attribute. Use `alt=""` (empty) for decorative images so they are ignored by assistive technology. +- **Loading & Performance**: + - Use `loading="lazy"` for images below the fold. + - Use `decoding="async"` to allow the browser to decode the image off the main thread. +- **Responsive Images**: Use `srcset` and `sizes` to provide different image resolutions for various viewports. +- **Art Direction**: Use the `<picture>` element with `<source>` tags for different image formats (e.g., WebP, AVIF) or viewports. + +### Video & Audio +- **Fallbacks**: Provide multiple `<source>` elements for different file formats. +- **Accessibility**: Use the `<track>` element for captions or subtitles (WebVTT). +- **Behavior**: + - Use the `poster` attribute for video placeholders. + - Use `muted` and `playsinline` for mobile-compatible autoplay. + +### SVG +- **Accessibility**: Use `aria-hidden="true"` for decorative SVGs and provide a `<title>` for meaningful graphics. + +## 6. Modern Web APIs + +- **`<dialog>` Element**: Use for modals and popovers. Employ `.showModal()` for modal behavior and `.show()` for non-modal views. Style the background using the `::backdrop` pseudo-element. +- **Web Components**: + - Use `<template>` for inert HTML fragments. + - Use `<slot>` as placeholders within a Shadow DOM. +- **Popover API**: Use the `popover` attribute and `popovertarget` for non-modal overlays. +- **`hidden` Attribute**: Use the `hidden` attribute to remove elements from view and the accessibility tree. + +## 7. Accessibility (A11y) + +- **Native Elements**: Prioritize native elements (e.g., `<button>`, `<a>`) over ARIA roles on generic containers (e.g., `<div role="button">`). +- **Interaction**: Use `<button>` for actions and `<a>` for navigation. +- **Focus**: Ensure a visible focus indicator is maintained for keyboard users. +- **ARIA Attributes**: Use the following for enhanced context: + - `aria-label`, `aria-labelledby`, `aria-describedby` for naming and descriptions. + - `aria-expanded`, `aria-hidden`, `aria-current`, `aria-live` for state and dynamic updates. +- **Skip Links**: Provide a link at the top of the page to skip directly to the main content. + +## 8. Scripts & Performance + +- **Script Loading**: Use `async` or `defer` attributes on `<script>` tags to prevent render-blocking. +- **Resource Placement**: + - Place `<link rel="stylesheet">` in the `<head>`. + - Place `<script>` tags with `defer` in the `<head>` or at the end of the `<body>`. + +## 9. Syntax & Best Practices + +- **Naming**: Use lowercase for all element and attribute names. +- **Quoting**: Use double quotes for all attribute values. +- **Elements**: + - Close all non-void tags. + - Treat void elements (e.g., `<img>`, `<br>`, `<hr>`, `<meta>`, `<link>`) as self-closing or use the standard short syntax. +- **Nesting**: Avoid deep DOM nesting to maintain performance and readability. diff --git a/.agents/skills/project-discipline-guides/mirrors/javascript/claude_mirror.md b/.agents/skills/project-discipline-guides/mirrors/javascript/claude_mirror.md deleted file mode 100644 index d0bf7f4a6..000000000 --- a/.agents/skills/project-discipline-guides/mirrors/javascript/claude_mirror.md +++ /dev/null @@ -1,842 +0,0 @@ -# JavaScript Best Practices, Syntax, and APIs — Common Knowledge Guide - -## 1. Variable Declarations - -### Use `const` by default, `let` when reassigning, never `var` -```javascript -const MAX_RETRIES = 3; // Immutable binding -let counter = 0; // Mutable binding -counter += 1; - -// Avoid: -var x = 5; // Function-scoped, hoisted, no TDZ -``` - -- `const` and `let` are block-scoped. -- `const` prevents reassignment, not mutation (`const arr = []; arr.push(1)` is fine). -- `var` should be avoided due to hoisting quirks and function scoping. -- Temporal Dead Zone (TDZ): `let`/`const` cannot be accessed before declaration. - -### Naming conventions -- `camelCase` for variables and functions. -- `PascalCase` for classes and constructors. -- `SCREAMING_SNAKE_CASE` for true constants (module-level primitives). -- `_prefix` historically signaled "private"; prefer `#privateField` in classes now. - -## 2. Strict Equality and Type Coercion - -### Always use `===` and `!==` -```javascript -if (value === null) { ... } -if (count !== 0) { ... } - -// Avoid: -if (value == null) // Loose equality (though == null is one common exception) -``` - -- `==` performs type coercion with surprising results (`[] == false`, `'' == 0`). -- The single legitimate use of `==` is `value == null` to check both `null` and `undefined`, but `value === null || value === undefined` is clearer. - -### `Object.is()` for special cases -```javascript -Object.is(NaN, NaN); // true (=== returns false) -Object.is(0, -0); // false (=== returns true) -``` - -## 3. Nullish Handling - -### Nullish coalescing `??` -```javascript -const port = config.port ?? 3000; // Falls back only on null/undefined -const name = input || 'default'; // Falls back on '', 0, false too -``` - -### Optional chaining `?.` -```javascript -const city = user?.address?.city; -const result = obj?.method?.(); -const item = arr?.[0]; -``` - -### Logical assignment operators -```javascript -a ??= b; // a = a ?? b -a ||= b; // a = a || b -a &&= b; // a = a && b -``` - -## 4. Functions - -### Arrow functions for callbacks and lexical `this` -```javascript -const double = (x) => x * 2; -const add = (a, b) => a + b; -const log = () => console.log('hi'); - -[1, 2, 3].map((n) => n * 2); -``` - -- Arrow functions don't have their own `this`, `arguments`, `super`, or `new.target`. -- Cannot be used as constructors. -- Use traditional `function` declarations for methods that need `this`, or for hoisted top-level functions. - -### Default parameters -```javascript -function greet(name = 'World', greeting = 'Hello') { - return `${greeting}, ${name}!`; -} -``` - -### Rest and spread -```javascript -function sum(...nums) { - return nums.reduce((a, b) => a + b, 0); -} - -const merged = [...arr1, ...arr2]; -const cloned = { ...original, override: true }; -fn(...args); -``` - -### Avoid `arguments` object -Use rest parameters `(...args)` instead — they're real arrays. - -## 5. Destructuring - -```javascript -// Object destructuring -const { name, age, email = 'n/a' } = user; -const { name: userName } = user; // Rename -const { a, ...rest } = obj; // Rest - -// Array destructuring -const [first, second, ...others] = list; -const [, , third] = list; // Skip -[a, b] = [b, a]; // Swap - -// Nested -const { address: { city } } = user; - -// In parameters -function render({ title, body, author = 'Anon' }) { ... } -``` - -## 6. Template Literals - -```javascript -const greeting = `Hello, ${name}!`; -const multiline = ` - Line 1 - Line 2 -`; - -// Tagged templates -const html = tag`<div>${value}</div>`; -``` - -- Always prefer template literals over string concatenation. - -## 7. Objects - -### Shorthand syntax -```javascript -const x = 1, y = 2; -const point = { x, y }; // Property shorthand - -const obj = { - greet() { return 'hi'; }, // Method shorthand - [`dynamic_${key}`]: value, // Computed property names -}; -``` - -### Object methods -```javascript -Object.keys(obj); -Object.values(obj); -Object.entries(obj); -Object.fromEntries(entries); -Object.assign(target, ...sources); // Prefer { ...spread } usually -Object.freeze(obj); // Shallow immutability -Object.hasOwn(obj, 'key'); // Modern replacement for hasOwnProperty -``` - -### Iterate with `Object.entries` -```javascript -for (const [key, value] of Object.entries(obj)) { - console.log(key, value); -} -``` - -## 8. Arrays - -### Prefer immutable, functional methods -```javascript -arr.map(fn); -arr.filter(fn); -arr.reduce(fn, initial); -arr.find(fn); -arr.findIndex(fn); -arr.findLast(fn); -arr.findLastIndex(fn); -arr.some(fn); -arr.every(fn); -arr.flat(depth); -arr.flatMap(fn); -arr.includes(value); -arr.at(-1); // Negative indexing -``` - -### Immutable counterparts to mutating methods (modern) -```javascript -arr.toSorted(); // Non-mutating sort -arr.toReversed(); // Non-mutating reverse -arr.toSpliced(start, deleteCount, ...items); -arr.with(index, value); // Replace at index -``` - -### Array creation -```javascript -Array.from(iterable); -Array.from({ length: 5 }, (_, i) => i); -Array.of(1, 2, 3); -[...iterable]; // Often the cleanest -``` - -### Avoid `for...in` for arrays -Use `for...of`, `forEach`, or index loops. `for...in` iterates enumerable properties (including inherited ones). - -## 9. Iteration - -```javascript -// for...of: values from any iterable -for (const item of iterable) { ... } - -// for...in: keys from object (rarely the right choice) -for (const key in obj) { - if (Object.hasOwn(obj, key)) { ... } -} - -// Classic for: when you need index control -for (let i = 0; i < arr.length; i++) { ... } - -// Entries for index + value -for (const [i, value] of arr.entries()) { ... } -``` - -## 10. Maps and Sets - -```javascript -const map = new Map(); -map.set(key, value); -map.get(key); -map.has(key); -map.delete(key); -map.size; -for (const [k, v] of map) { ... } - -const set = new Set([1, 2, 3]); -set.add(4); -set.has(2); -set.delete(1); -[...new Set(arr)]; // Deduplicate - -// WeakMap / WeakSet for keys held weakly (no enumeration, GC-friendly) -const cache = new WeakMap(); -``` - -Use `Map` over plain objects when: -- Keys aren't strings/symbols. -- You need ordered iteration. -- You need frequent add/delete operations. -- You need a known size. - -## 11. Async / Await and Promises - -### Prefer async/await over `.then()` chains -```javascript -async function loadUser(id) { - try { - const res = await fetch(`/users/${id}`); - if (!res.ok) throw new Error(`HTTP ${res.status}`); - return await res.json(); - } catch (err) { - console.error('Failed to load user', err); - throw err; - } -} -``` - -### Parallel awaits -```javascript -// Sequential (slow) -const a = await fetchA(); -const b = await fetchB(); - -// Parallel (fast) -const [a, b] = await Promise.all([fetchA(), fetchB()]); -``` - -### Promise combinators -```javascript -Promise.all(promises); // All succeed or first rejection -Promise.allSettled(promises); // Wait for all, regardless -Promise.race(promises); // First to settle -Promise.any(promises); // First to fulfill -``` - -### Top-level await -Available in ES modules. No need to wrap in an IIFE. - -### Async iteration -```javascript -for await (const chunk of stream) { ... } - -async function* generate() { - yield await fetch(...); -} -``` - -### Never forget to await -Floating promises swallow errors. Use `void promise` to explicitly ignore, or `await` it. - -## 12. Error Handling - -### Throw `Error` instances -```javascript -throw new Error('Descriptive message'); -throw new TypeError('Expected number'); - -// Custom errors -class ValidationError extends Error { - constructor(message, field) { - super(message); - this.name = 'ValidationError'; - this.field = field; - } -} -``` - -### `Error.cause` for chaining -```javascript -try { - doThing(); -} catch (err) { - throw new Error('Higher-level failure', { cause: err }); -} -``` - -### `try/catch` without binding -```javascript -try { ... } catch { ... } // Optional binding -``` - -### `AggregateError` for multiple errors -Returned by `Promise.any` when all fail. - -## 13. Modules (ESM) - -```javascript -// Named exports -export function foo() {} -export const BAR = 1; -export { existing as renamed }; - -// Default export -export default class App {} - -// Imports -import App, { foo, BAR as CONST } from './module.js'; -import * as utils from './utils.js'; - -// Dynamic imports (returns a Promise) -const mod = await import('./lazy.js'); - -// Re-exports -export { foo } from './other.js'; -export * from './other.js'; -``` - -- Prefer named exports; they aid refactoring and tree-shaking. -- Use `.js` extensions in import paths for native ESM. -- One responsibility per module. - -## 14. Classes - -```javascript -class Counter { - #count = 0; // Private field - static instances = 0; // Static field - - constructor(start = 0) { - this.#count = start; - Counter.instances++; - } - - get value() { return this.#count; } // Getter - set value(v) { this.#count = v; } // Setter - - increment() { this.#count++; return this; } - - static create(start) { // Static method - return new Counter(start); - } - - #privateMethod() { ... } // Private method -} - -class Timer extends Counter { - constructor() { - super(0); - } -} -``` - -- Use `#field` for true privacy (enforced by the engine). -- Prefer composition over inheritance. -- Don't add methods to prototypes manually — use `class` syntax. - -## 15. Symbols and Well-Known Symbols - -```javascript -const id = Symbol('id'); -obj[id] = 123; - -// Well-known symbols enable customization -class Range { - *[Symbol.iterator]() { yield 1; yield 2; } -} -``` - -## 16. Generators and Iterators - -```javascript -function* range(start, end) { - for (let i = start; i < end; i++) yield i; -} - -for (const n of range(0, 5)) { ... } -[...range(0, 5)]; -``` - -## 17. Numbers, BigInt, Math - -```javascript -Number.isInteger(x); -Number.isFinite(x); -Number.isNaN(x); // Reliable, unlike global isNaN -Number.parseInt(str, 10); // Always pass radix -Number.parseFloat(str); -Number.EPSILON; -Number.MAX_SAFE_INTEGER; - -// Numeric separators for readability -const million = 1_000_000; - -// BigInt for arbitrary precision integers -const big = 9007199254740993n; -big + 1n; - -Math.trunc(x); -Math.sign(x); -Math.hypot(a, b); -Math.clz32(x); -``` - -### Floating-point comparison -```javascript -Math.abs(a - b) < Number.EPSILON; -``` - -## 18. Strings - -```javascript -str.startsWith(prefix); -str.endsWith(suffix); -str.includes(sub); -str.padStart(targetLength, padChar); -str.padEnd(targetLength, padChar); -str.repeat(n); -str.trim(); -str.trimStart(); -str.trimEnd(); -str.replaceAll(search, replacement); -str.at(-1); -str.normalize('NFC'); // Unicode normalization -str.matchAll(regex); // Returns iterator -``` - -### String iteration (Unicode-aware) -```javascript -[...'😀'].length; // Correctly handles surrogates -for (const char of str) { ... } -``` - -## 19. Regular Expressions - -```javascript -const re = /pattern/giu; // u flag for Unicode - -// Named capture groups -const { groups: { year, month } } = '2026-05'.match(/(?<year>\d{4})-(?<month>\d{2})/); - -// Lookbehind -/(?<=\$)\d+/ - -// Sticky flag -/foo/y -``` - -- Use `u` flag for Unicode-correct matching. -- Use `s` flag (dotAll) when `.` should match newlines. -- Prefer `String.matchAll` over `RegExp.exec` loops. - -## 20. JSON - -```javascript -JSON.stringify(value, null, 2); // Pretty-print with 2-space indent -JSON.stringify(value, replacer); -JSON.parse(text, reviver); -``` - -- Wrap `JSON.parse` in try/catch when parsing untrusted input. -- `undefined`, functions, and Symbols are dropped during stringify. - -## 21. Dates - -```javascript -const now = new Date(); -now.toISOString(); // '2026-05-12T...' -Date.now(); // Epoch ms -``` - -- For non-trivial date logic, prefer libraries (date-fns, dayjs, Luxon) or the upcoming `Temporal` API where available. -- `Intl.DateTimeFormat` for locale-aware formatting: -```javascript -new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' }).format(date); -``` - -## 22. Internationalization (`Intl`) - -```javascript -new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(1234.5); -new Intl.RelativeTimeFormat('en').format(-3, 'day'); // '3 days ago' -new Intl.ListFormat('en').format(['A', 'B', 'C']); // 'A, B, and C' -new Intl.PluralRules('en').select(2); // 'other' -new Intl.Collator('en').compare('a', 'b'); -new Intl.Segmenter('en', { granularity: 'word' }); -``` - -## 23. Fetch API - -```javascript -const res = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - signal: controller.signal, -}); - -if (!res.ok) throw new Error(`HTTP ${res.status}`); -const data = await res.json(); -``` - -- `fetch` only rejects on network errors — always check `res.ok`. -- Use `AbortController` for cancellation and timeouts: -```javascript -const controller = new AbortController(); -setTimeout(() => controller.abort(), 5000); -fetch(url, { signal: controller.signal }); - -// Or built-in: -fetch(url, { signal: AbortSignal.timeout(5000) }); -``` - -## 24. URL and URLSearchParams - -```javascript -const url = new URL('/path', 'https://example.com'); -url.searchParams.set('q', 'hello'); -url.searchParams.append('tag', 'js'); -url.toString(); - -const params = new URLSearchParams(window.location.search); -params.get('id'); -``` - -Don't manually concatenate query strings. - -## 25. DOM (Browser) - -### Selection -```javascript -document.querySelector('.btn'); -document.querySelectorAll('li'); -element.closest('.container'); -element.matches('.active'); -``` - -### Manipulation -```javascript -element.classList.add('active'); -element.classList.toggle('open', isOpen); -element.dataset.userId; // Reads data-user-id -element.replaceChildren(...nodes); -element.append(child); // Accepts strings & nodes -element.prepend(child); -element.before(node); -element.after(node); -element.remove(); -``` - -### Events -```javascript -element.addEventListener('click', handler, { once: true, passive: true, signal }); - -// Use AbortController to remove multiple listeners at once -const controller = new AbortController(); -el.addEventListener('click', h, { signal: controller.signal }); -controller.abort(); // Removes them all - -// Event delegation -container.addEventListener('click', (e) => { - const btn = e.target.closest('button'); - if (!btn) return; - ... -}); -``` - -### Avoid `innerHTML` with untrusted input (XSS risk) -Prefer `textContent`, `append`, or sanitize with a library. - -## 26. Web APIs (Common) - -```javascript -// Storage -localStorage.setItem('key', JSON.stringify(value)); -JSON.parse(localStorage.getItem('key')); -sessionStorage; - -// Timers -setTimeout(fn, ms); -setInterval(fn, ms); -queueMicrotask(fn); // Microtask queue -requestAnimationFrame(fn); // Before next paint -requestIdleCallback(fn); // When idle - -// Crypto -crypto.randomUUID(); -crypto.getRandomValues(new Uint8Array(16)); -await crypto.subtle.digest('SHA-256', data); - -// Observers -new IntersectionObserver(cb).observe(el); -new ResizeObserver(cb).observe(el); -new MutationObserver(cb).observe(el, { childList: true }); - -// Structured cloning -const clone = structuredClone(obj); // Deep clone, handles cycles, Maps, etc. -``` - -## 27. Modern Cloning and Equality - -- `structuredClone(value)` for deep clones (no JSON round-trip needed). -- `{ ...obj }` and `[...arr]` for shallow clones. -- Reference equality only — no built-in deep equality. - -## 28. Typing and Documentation - -### JSDoc for type hints in plain JS -```javascript -/** - * @param {string} name - * @param {number} [age] - * @returns {Promise<User>} - */ -async function findUser(name, age) { ... } -``` - -- Editors (VS Code) understand JSDoc and provide IntelliSense. -- For larger projects, consider TypeScript. - -## 29. Clean Code Principles - -### Naming -- Use intention-revealing names: `daysSinceLogin`, not `d`. -- Booleans read as questions: `isReady`, `hasPermission`, `canEdit`. -- Functions are verbs: `getUser`, `renderList`, `parseJSON`. -- Avoid abbreviations; favor clarity over brevity. -- Plural for collections (`users`), singular for items (`user`). - -### Functions -- Single Responsibility: one function, one reason to change. -- Keep functions small; extract when they do >1 thing. -- Prefer pure functions: same input → same output, no side effects. -- Limit parameters (≤3); use options objects when more are needed. -- Avoid boolean flag parameters that change behavior — split into two functions. -- Return early; avoid deep nesting. - -```javascript -// Guard clauses -function process(user) { - if (!user) return null; - if (!user.active) return null; - ... -} -``` - -### Avoid magic numbers and strings -```javascript -const MAX_LOGIN_ATTEMPTS = 5; -const STATUS_PENDING = 'pending'; -``` - -### Immutability -- Don't mutate function arguments. -- Prefer non-mutating array methods (`map`, `filter`, `toSorted`). -- Treat data as immutable; return new objects. - -### Composition over inheritance -- Small reusable functions composed together generally beat deep class hierarchies. -- Mixins via plain objects/functions when sharing behavior. - -### Don't repeat yourself — but don't over-abstract -- Three similar usages is better than a premature abstraction. -- Wait until the right shape is obvious. - -### Comments -- Code should be self-documenting. -- Comments explain *why*, not *what*. -- Delete commented-out code — version control remembers. -- Use `// TODO:` and `// FIXME:` sparingly; track in an issue tracker. - -### Error handling -- Fail fast at boundaries (validate input, throw early). -- Don't swallow errors; either handle or rethrow. -- Use specific error types so callers can discriminate. -- `try/catch` should wrap the smallest possible block. - -### Avoid global state -- Use modules; export only what's needed. -- Side effects belong at edges, not in pure logic. - -### Avoid `eval`, `Function` constructor, and `with` -Security hazards and prevent optimization. - -## 30. Performance Defaults - -- Cache repeated DOM lookups in variables. -- Batch DOM writes (use `DocumentFragment` or `replaceChildren`). -- Debounce/throttle high-frequency events (input, scroll, resize). -- Use `passive: true` on touch/wheel listeners that don't `preventDefault`. -- Lazy-load with dynamic `import()`. -- Use `Map`/`Set` over arrays for membership lookups. -- Memoize pure expensive computations. -- Avoid premature optimization — measure first (`performance.now()`, DevTools). - -## 31. Security Defaults - -- Never trust user input — validate and sanitize. -- Use parameterized queries; never string-concat SQL. -- Avoid `innerHTML` with untrusted content; prefer `textContent`. -- Use `crypto.randomUUID()` and `crypto.getRandomValues` — never `Math.random` for tokens/IDs requiring uniqueness or unpredictability. -- Set `Content-Security-Policy`, `X-Content-Type-Options`, etc. on the server. -- Use `rel="noopener noreferrer"` on `target="_blank"` links. -- Hash passwords with bcrypt/argon2 server-side; never roll your own crypto. - -## 32. Testing Hygiene - -- Test behavior, not implementation. -- Arrange / Act / Assert structure. -- One logical assertion per test. -- Descriptive test names: `it('returns null when user is inactive')`. -- Avoid shared mutable state between tests. -- Mock at module boundaries; prefer real objects when possible. - -## 33. Tooling Defaults - -- Use a formatter (Prettier) — don't bikeshed style. -- Use a linter (ESLint) with sensible defaults. -- Pin dependency versions in `package-lock.json` / `pnpm-lock.yaml`. -- Use `"type": "module"` in `package.json` for native ESM. -- Use `node:` prefix for Node built-ins: `import fs from 'node:fs/promises';`. - -## 34. Async Patterns Worth Knowing - -### Sequential async with reduce -```javascript -await items.reduce(async (prev, item) => { - await prev; - await process(item); -}, Promise.resolve()); -``` - -### Concurrent with limit -Often via libraries (`p-limit`); avoid hand-rolling. - -### Avoid `async` in `forEach` -`forEach` ignores returned promises. Use `for...of` + `await`, or `Promise.all(arr.map(async ...))`. - -## 35. Common Pitfalls to Avoid - -- Mutating a parameter (`function(arr) { arr.push(...) }`). -- Forgetting `await` before an async call. -- Using `==` instead of `===`. -- Using `for...in` on arrays. -- Calling `.length` repeatedly on huge arrays inside loops. -- Confusing `null` and `undefined` (prefer `undefined` for "not set"; reserve `null` for "intentional empty"). -- Using `parseInt` without radix. -- Capturing loop variables with `var` in closures (use `let`). -- Returning from inside `forEach` expecting it to break (it doesn't). -- Comparing objects/arrays with `===` (reference equality only). -- Mixing tabs and spaces. -- Leaking secrets into client-side code. - -## 36. Modern Syntax Quick Reference - -```javascript -// Numeric separators -1_000_000 - -// Logical assignment -a ??= b; a ||= b; a &&= b; - -// Optional chaining -obj?.prop?.method?.() - -// Nullish coalescing -value ?? fallback - -// Object spread -{ ...a, ...b } - -// Top-level await (in modules) -const data = await fetch(url).then(r => r.json()); - -// Private class fields -class C { #x = 0; } - -// Static blocks -class C { static { /* init */ } } - -// Error cause -new Error('msg', { cause: err }) - -// Array.prototype.at -arr.at(-1) - -// Object.hasOwn -Object.hasOwn(obj, key) - -// String.prototype.replaceAll -str.replaceAll('a', 'b') - -// Array immutable methods -arr.toSorted(); arr.toReversed(); arr.with(i, v); - -// structuredClone -structuredClone(obj) -``` - ---- - -This represents the baseline of "common JavaScript knowledge" I apply by default — modern (ES2020+) features that have shipped across all evergreen browsers, plus standard clean-code conventions widely recognized in the JS ecosystem. diff --git a/.agents/skills/project-discipline-guides/mirrors/javascript/codex_mirror.md b/.agents/skills/project-discipline-guides/mirrors/javascript/codex_mirror.md deleted file mode 100644 index b713cb6ee..000000000 --- a/.agents/skills/project-discipline-guides/mirrors/javascript/codex_mirror.md +++ /dev/null @@ -1,1941 +0,0 @@ -Below is a redundancy mirror of JavaScript knowledge I would treat as broadly standard, default, and generally “common knowledge” when writing modern JavaScript. - -# JavaScript Redundancy Mirror - -## Baseline Assumptions - -Modern JavaScript should generally mean: - -- ECMAScript modules by default. -- `let` and `const`, not `var`. -- Strict-mode semantics via modules. -- Promises and `async` / `await`. -- Modern collection types: `Map`, `Set`, `WeakMap`, `WeakSet`. -- Modern object and array APIs. -- Browser-native DOM APIs rather than legacy libraries for basic DOM work. -- Feature detection for APIs that may not exist everywhere. -- Progressive enhancement for newer platform features. -- Avoiding transpilation unless project constraints require older browser support. -- Avoiding polyfills unless they are necessary and intentionally scoped. - -JavaScript code should be clear, predictable, side-effect-conscious, and written for maintainability rather than cleverness. - -## Language Basics - -Use `const` by default. - -```js -const value = computeValue(); -``` - -Use `let` when reassignment is required. - -```js -let count = 0; -count += 1; -``` - -Avoid `var`. - -Use semicolons consistently if the project uses them. If not, understand ASI hazards and avoid ambiguous leading tokens like `(`, `[`, `/`, `+`, and `-` at statement boundaries. - -Use strict equality by default. - -```js -if (id === selectedId) {} -if (value !== null) {} -``` - -Avoid `==` and `!=` except for the deliberate `value == null` pattern when checking both `null` and `undefined`. - -```js -if (value == null) { - // null or undefined -} -``` - -Prefer explicit boolean logic over truthiness when empty string, `0`, or `false` are valid values. - -```js -if (name !== "") {} -if (count > 0) {} -if (enabled === true) {} -``` - -Use template literals for interpolation and multiline strings. - -```js -const message = `Hello, ${name}`; -``` - -Use numeric separators for readability. - -```js -const timeout = 10_000; -const maxBytes = 5_242_880; -``` - -Use `BigInt` only when integer precision beyond `Number.MAX_SAFE_INTEGER` is required. - -```js -const id = 9007199254740993n; -``` - -Do not mix `BigInt` and `Number` without explicit conversion. - -## Variables And Scope - -Prefer the narrowest possible scope. - -```js -if (shouldRun) { - const result = run(); -} -``` - -Avoid mutable shared state. - -Avoid reusing variables for different meanings. - -```js -const user = getUser(); -const profile = getProfile(user.id); -``` - -Avoid assigning to undeclared variables. - -Avoid global variables. If global state is necessary, isolate it behind a module or explicit API. - -Prefer named constants for meaningful magic values. - -```js -const MAX_RETRIES = 3; -const RETRY_DELAY_MS = 250; -``` - -## Functions - -Prefer small, single-purpose functions. - -```js -function formatUserName(user) { - return `${user.firstName} ${user.lastName}`; -} -``` - -Use function declarations for top-level named functions when hoisting is useful. - -```js -function parseConfig(input) { - return JSON.parse(input); -} -``` - -Use arrow functions for callbacks and small expressions. - -```js -items.map((item) => item.id); -``` - -Do not use arrow functions when dynamic `this`, `arguments`, or constructor behavior is needed. - -Use default parameters instead of manual fallback logic. - -```js -function createUser({ role = "user" } = {}) {} -``` - -Be careful with default values: they only apply to `undefined`, not `null`. - -```js -function greet(name = "Guest") {} -greet(null); // name is null -``` - -Prefer object parameters for functions with multiple optional values. - -```js -function createRequest({ method = "GET", headers = {}, body } = {}) {} -``` - -Avoid boolean parameter flags when they make call sites unclear. - -```js -// Less clear -renderUser(user, true); - -// Clearer -renderUser(user, { compact: true }); -``` - -Return early to reduce nesting. - -```js -function getLabel(value) { - if (value == null) return ""; - if (value === "") return "Empty"; - return String(value); -} -``` - -Prefer pure functions where practical. - -Avoid functions that both compute a value and mutate external state unless that is clearly their purpose. - -Use rest parameters instead of `arguments`. - -```js -function sum(...values) { - return values.reduce((total, value) => total + value, 0); -} -``` - -Use spread syntax for argument expansion. - -```js -Math.max(...values); -``` - -## Objects - -Use object literals for plain objects. - -```js -const user = { - id, - name, - active: true, -}; -``` - -Use property shorthand. - -```js -const id = "123"; -const name = "Ada"; -const user = { id, name }; -``` - -Use method shorthand. - -```js -const service = { - start() {}, - stop() {}, -}; -``` - -Use computed property names when needed. - -```js -const field = "email"; -const data = { - [field]: value, -}; -``` - -Use destructuring for clear extraction. - -```js -const { id, name } = user; -``` - -Use defaults with destructuring. - -```js -const { role = "user" } = user; -``` - -Use renaming when names conflict. - -```js -const { id: userId } = user; -``` - -Avoid destructuring so deeply that readability suffers. - -Prefer optional chaining for safe property access. - -```js -const city = user.address?.city; -``` - -Prefer nullish coalescing for defaults where `0`, `false`, or `""` are valid. - -```js -const limit = options.limit ?? 20; -``` - -Avoid using `||` for defaults unless all falsy values should trigger the fallback. - -```js -const label = input || "Untitled"; -``` - -Use object spread for shallow copies and updates. - -```js -const nextUser = { - ...user, - name: "Grace", -}; -``` - -Remember object spread is shallow. - -Avoid mutating input objects unless mutation is explicit and documented. - -Use `Object.freeze()` for shallow immutability when appropriate. - -Use `Object.assign()` when that is clearer or needed for target mutation. - -```js -Object.assign(target, source); -``` - -Use `Object.keys()`, `Object.values()`, and `Object.entries()` for object iteration. - -```js -for (const [key, value] of Object.entries(record)) {} -``` - -Use `Object.fromEntries()` to build objects from key-value pairs. - -```js -const byId = Object.fromEntries(users.map((user) => [user.id, user])); -``` - -Use `Object.hasOwn()` instead of `obj.hasOwnProperty()`. - -```js -if (Object.hasOwn(config, "timeout")) {} -``` - -Avoid relying on property enumeration order unless the behavior is specifically defined and appropriate. - -Use `structuredClone()` for deep cloning supported data types. - -```js -const copy = structuredClone(value); -``` - -Do not use `JSON.parse(JSON.stringify(value))` as a general deep clone because it loses types and fails on unsupported values. - -## Arrays - -Use array literals. - -```js -const items = []; -``` - -Use `Array.from()` to create arrays from iterables or array-like values. - -```js -const nodes = Array.from(document.querySelectorAll(".item")); -``` - -Use spread for shallow copies. - -```js -const copy = [...items]; -``` - -Use `map()` for one-to-one transformations. - -```js -const ids = users.map((user) => user.id); -``` - -Use `filter()` for selection. - -```js -const activeUsers = users.filter((user) => user.active); -``` - -Use `find()` for the first matching item. - -```js -const selected = users.find((user) => user.id === id); -``` - -Use `some()` and `every()` for predicates. - -```js -const hasErrors = fields.some((field) => field.error); -const allValid = fields.every((field) => field.valid); -``` - -Use `reduce()` when it genuinely expresses accumulation, but avoid overly clever reducers. - -```js -const total = items.reduce((sum, item) => sum + item.price, 0); -``` - -Prefer simple loops when they are clearer. - -```js -const results = []; - -for (const item of items) { - if (!item.active) continue; - results.push(transform(item)); -} -``` - -Use `flat()` and `flatMap()` for flattening. - -```js -const tags = posts.flatMap((post) => post.tags); -``` - -Use `includes()` instead of `indexOf(...) !== -1`. - -```js -if (allowedRoles.includes(role)) {} -``` - -Use `at()` for relative indexing. - -```js -const last = items.at(-1); -``` - -Use modern non-mutating array methods where available: - -```js -const sorted = items.toSorted((a, b) => a.name.localeCompare(b.name)); -const reversed = items.toReversed(); -const next = items.with(index, updatedItem); -const trimmed = items.toSpliced(index, 1); -``` - -Use mutating methods intentionally: - -```js -items.push(item); -items.sort(compare); -items.splice(index, 1); -``` - -Do not mutate arrays passed into functions unless mutation is the explicit contract. - -Always provide a comparator for numeric sorting. - -```js -numbers.toSorted((a, b) => a - b); -``` - -Do not rely on default sort for numbers. - -```js -[10, 2, 1].sort(); // lexical, not numeric -``` - -Use stable identifiers as keys when rendering lists in UI frameworks; do not use indexes when order can change. - -Use `Array.isArray()` instead of `instanceof Array`. - -```js -if (Array.isArray(value)) {} -``` - -## Strings - -Use `trim()`, `trimStart()`, and `trimEnd()` for whitespace cleanup. - -```js -const normalized = input.trim(); -``` - -Use `startsWith()`, `endsWith()`, and `includes()`. - -```js -if (path.startsWith("/api/")) {} -``` - -Use `replaceAll()` for simple global replacement. - -```js -const slug = title.toLowerCase().replaceAll(" ", "-"); -``` - -Use regular expressions for pattern-based replacement. - -```js -const slug = title.toLowerCase().replace(/\s+/g, "-"); -``` - -Use `padStart()` and `padEnd()` for formatting. - -```js -const minutes = String(date.getMinutes()).padStart(2, "0"); -``` - -Use `Intl.Collator` or `localeCompare()` for human-facing sorting. - -```js -const collator = new Intl.Collator(undefined, { sensitivity: "base" }); -names.toSorted((a, b) => collator.compare(a, b)); -``` - -Use Unicode-aware approaches when user-facing text matters. Avoid assuming `.length` equals visual character count. - -## Numbers And Math - -Use `Number.isNaN()` instead of global `isNaN()`. - -```js -if (Number.isNaN(value)) {} -``` - -Use `Number.isFinite()` instead of global `isFinite()`. - -```js -if (Number.isFinite(value)) {} -``` - -Use `Number.isInteger()` and `Number.isSafeInteger()` when appropriate. - -```js -if (!Number.isSafeInteger(id)) {} -``` - -Use `Number.parseInt()` and `Number.parseFloat()`. - -```js -const count = Number.parseInt(input, 10); -``` - -Always pass radix to `parseInt`. - -Use `Math.trunc()`, `Math.round()`, `Math.floor()`, and `Math.ceil()` intentionally. - -Use `Math.min()` and `Math.max()` with spread for reasonable array sizes. - -```js -const max = Math.max(...values); -``` - -Avoid floating-point equality for decimal calculations. - -```js -Math.abs(a - b) < Number.EPSILON; -``` - -Do not use JavaScript floating-point arithmetic for exact money math without a deliberate integer, decimal, or library strategy. - -## Dates And Time - -Use `Date` for basic timestamps and interoperability. - -```js -const now = new Date(); -``` - -Use ISO 8601 strings for serialization. - -```js -const value = new Date().toISOString(); -``` - -Use epoch milliseconds for simple comparisons. - -```js -if (end.getTime() > start.getTime()) {} -``` - -Avoid parsing ambiguous date strings. - -```js -new Date("2026-05-12T12:00:00Z"); -``` - -Prefer explicit time zones for user-facing date/time behavior. - -Use `Intl.DateTimeFormat` for localized formatting. - -```js -const formatter = new Intl.DateTimeFormat(undefined, { - dateStyle: "medium", - timeStyle: "short", -}); -``` - -Be careful with `Date` month indexes: months are zero-based in constructor overloads. - -Avoid manual date math where time zones and daylight saving time matter. - -Use `Temporal` when available or via polyfill for robust date/time modeling, especially as a progressive enhancement or in environments where it is supported. - -## Regular Expressions - -Use regex literals for static patterns. - -```js -const emailLike = /\S+@\S+\.\S+/; -``` - -Use `RegExp` constructor for dynamic patterns and escape user input before interpolation. - -Use named capture groups for clarity. - -```js -const match = input.match(/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/); -``` - -Use the `u` flag for Unicode-aware matching when appropriate. - -```js -const pattern = /\p{Letter}+/gu; -``` - -Use `matchAll()` for repeated captures. - -```js -for (const match of text.matchAll(/#(\w+)/g)) {} -``` - -Avoid complex regexes when parsing would be clearer and safer. - -## Modules - -Use ECMAScript modules. - -```js -export function parse() {} -export const VERSION = "1.0.0"; -``` - -```js -import { parse } from "./parse.js"; -``` - -Prefer named exports for most shared utilities. - -Use default exports when a module has one primary concept. - -Avoid circular dependencies. - -Keep module boundaries clear. - -Avoid modules with large hidden side effects. - -Use dynamic `import()` for lazy loading. - -```js -const { renderChart } = await import("./chart.js"); -``` - -Use import assertions/attributes where required by the runtime for JSON or other module types, subject to support. - -Use top-level `await` in modules only when it is appropriate and does not unnecessarily block loading. - -## Classes And Prototypes - -Use classes when modeling stateful entities with behavior. - -```js -class Store { - #items = []; - - add(item) { - this.#items.push(item); - } - - get items() { - return [...this.#items]; - } -} -``` - -Use private fields for internal state. - -```js -class Counter { - #count = 0; - - increment() { - this.#count += 1; - } -} -``` - -Use static methods for class-level helpers. - -```js -class User { - static fromJSON(value) { - return new User(value); - } -} -``` - -Avoid deep inheritance hierarchies. - -Prefer composition over inheritance. - -Use `extends` for real subtype relationships. - -Avoid modifying built-in prototypes. - -Avoid relying on `this` in callbacks unless intentionally bound. - -```js -button.addEventListener("click", this.handleClick.bind(this)); -``` - -or: - -```js -handleClick = () => {}; -``` - -where class field syntax is supported by the project toolchain/runtime. - -## Error Handling - -Throw `Error` objects, not strings. - -```js -throw new Error("Invalid user ID"); -``` - -Use custom error classes when callers need to distinguish error types. - -```js -class ValidationError extends Error { - constructor(message, details) { - super(message); - this.name = "ValidationError"; - this.details = details; - } -} -``` - -Preserve causes with `cause`. - -```js -throw new Error("Failed to load config", { cause: error }); -``` - -Use `try` / `catch` around operations that can fail and that you can meaningfully handle. - -```js -try { - return await loadUser(id); -} catch (error) { - logger.error(error); - return null; -} -``` - -Do not swallow errors silently. - -Avoid catching errors only to rethrow them unchanged. - -Use `finally` for cleanup. - -```js -try { - await lock.acquire(); -} finally { - lock.release(); -} -``` - -Validate external inputs at boundaries. - -Fail early when required invariants are missing. - -Make error messages actionable. - -Do not expose sensitive details in user-facing errors. - -## Promises And Async - -Use `async` / `await` for asynchronous control flow. - -```js -async function loadData() { - const response = await fetch("/api/data"); - return response.json(); -} -``` - -Always handle promise rejections. - -```js -loadData().catch(reportError); -``` - -Use `Promise.all()` for independent concurrent work where all must succeed. - -```js -const [user, posts] = await Promise.all([ - fetchUser(id), - fetchPosts(id), -]); -``` - -Use `Promise.allSettled()` when all outcomes matter. - -```js -const results = await Promise.allSettled(tasks); -``` - -Use `Promise.race()` for first-settled behavior. - -Use `Promise.any()` for first-fulfilled behavior. - -Avoid `await` in a loop when operations can run concurrently. - -```js -const results = await Promise.all(items.map(processItem)); -``` - -Use sequential `await` in loops when order, rate limits, or dependencies matter. - -```js -for (const item of items) { - await processItem(item); -} -``` - -Do not use `Array.prototype.forEach()` with async callbacks when awaiting completion is needed. - -```js -// Avoid -items.forEach(async (item) => { - await processItem(item); -}); -``` - -Use `for...of` or `Promise.all`. - -Use `AbortController` for cancellable async operations. - -```js -const controller = new AbortController(); - -fetch(url, { signal: controller.signal }); - -controller.abort(); -``` - -Use timeout helpers carefully. - -```js -function timeout(ms) { - return new Promise((_, reject) => { - setTimeout(() => reject(new Error("Timed out")), ms); - }); -} -``` - -Prefer APIs with built-in cancellation where available. - -## Iteration And Iterables - -Use `for...of` for iterable values. - -```js -for (const item of items) {} -``` - -Use `for...in` only for object property names, and usually prefer `Object.keys()` or `Object.entries()`. - -Use generators for lazy sequences. - -```js -function* range(start, end) { - for (let value = start; value < end; value += 1) { - yield value; - } -} -``` - -Use async iterators for streaming async data. - -```js -for await (const chunk of stream) {} -``` - -Understand that arrays, strings, maps, sets, NodeLists in modern browsers, and many platform objects are iterable. - -## Maps, Sets, WeakMaps, WeakSets - -Use `Map` when keys are not naturally strings or when insertion order and frequent additions/removals matter. - -```js -const usersById = new Map(); -usersById.set(user.id, user); -``` - -Use `Set` for uniqueness. - -```js -const uniqueIds = new Set(ids); -``` - -Use `WeakMap` for metadata keyed by objects without preventing garbage collection. - -```js -const metadata = new WeakMap(); -metadata.set(element, { initialized: true }); -``` - -Use `WeakSet` for object membership tracking without retaining objects. - -Prefer `map.has(key)` over checking `map.get(key) !== undefined` when `undefined` can be a stored value. - -```js -if (cache.has(key)) {} -``` - -Convert maps and sets when needed. - -```js -const entries = [...map.entries()]; -const values = [...set]; -``` - -## JSON And Structured Data - -Use `JSON.stringify()` and `JSON.parse()` for JSON. - -```js -const json = JSON.stringify(data); -const data = JSON.parse(json); -``` - -Wrap `JSON.parse()` for untrusted input. - -```js -function parseJSON(value) { - try { - return JSON.parse(value); - } catch { - return null; - } -} -``` - -Use replacer and reviver when needed. - -```js -JSON.stringify(value, null, 2); -``` - -Remember JSON does not support `undefined`, functions, symbols, `BigInt`, cyclic references, `Map`, `Set`, or rich object prototypes. - -Use `structuredClone()` for platform-supported structured cloning. - -Use `FormData`, `URLSearchParams`, `Blob`, `File`, `ArrayBuffer`, and typed arrays for browser-native data handling where appropriate. - -## DOM Selection And Manipulation - -Use `querySelector()` and `querySelectorAll()` for CSS selector-based queries. - -```js -const button = document.querySelector("[data-submit]"); -const items = document.querySelectorAll(".item"); -``` - -Check for `null` when an element may not exist. - -```js -const button = document.querySelector("button"); - -if (button) { - button.disabled = true; -} -``` - -Prefer `textContent` for text. - -```js -element.textContent = label; -``` - -Use `innerHTML` only with trusted or sanitized content. - -Avoid injecting unsanitized user content into HTML. - -Use `classList` for classes. - -```js -element.classList.add("active"); -element.classList.toggle("hidden", isHidden); -``` - -Use `dataset` for `data-*` attributes. - -```js -const id = element.dataset.id; -``` - -Use `setAttribute()` and `removeAttribute()` for attributes where properties are not appropriate. - -Use DOM properties for common reflected properties. - -```js -input.value = ""; -button.disabled = true; -``` - -Use `createElement()` for creating elements. - -```js -const item = document.createElement("li"); -item.textContent = name; -``` - -Use `DocumentFragment` or batch DOM updates for large insertions. - -```js -const fragment = document.createDocumentFragment(); -``` - -Use `replaceChildren()` to replace content. - -```js -list.replaceChildren(...items); -``` - -Use `closest()` for ancestor lookup. - -```js -const row = event.target.closest("[data-row]"); -``` - -Use `matches()` for selector checks. - -```js -if (element.matches(".active")) {} -``` - -Avoid layout thrashing by batching reads and writes. - -```js -const width = element.offsetWidth; -element.style.width = `${width + 10}px`; -``` - -Use `requestAnimationFrame()` for visual updates. - -```js -requestAnimationFrame(() => { - element.style.transform = "translateX(10px)"; -}); -``` - -Use `MutationObserver`, `ResizeObserver`, and `IntersectionObserver` instead of polling when appropriate. - -## Events - -Use `addEventListener()`. - -```js -button.addEventListener("click", handleClick); -``` - -Remove listeners when no longer needed. - -```js -button.removeEventListener("click", handleClick); -``` - -Use event delegation for many similar child elements. - -```js -list.addEventListener("click", (event) => { - const button = event.target.closest("[data-action]"); - if (!button) return; -}); -``` - -Use options such as `once`, `passive`, and `signal`. - -```js -element.addEventListener("click", handler, { once: true }); - -const controller = new AbortController(); -element.addEventListener("click", handler, { signal: controller.signal }); -controller.abort(); -``` - -Use passive listeners for scroll/touch events when `preventDefault()` is not needed. - -```js -window.addEventListener("scroll", onScroll, { passive: true }); -``` - -Understand event bubbling and capturing. - -Use `event.currentTarget` when referring to the element the listener is attached to. - -Use `event.target` when referring to the originating element. - -Avoid inline HTML event handlers. - -## Fetch And Networking - -Use `fetch()` for HTTP requests. - -```js -const response = await fetch("/api/users"); -``` - -Check `response.ok`; `fetch` does not reject for HTTP error status codes. - -```js -if (!response.ok) { - throw new Error(`Request failed: ${response.status}`); -} -``` - -Parse based on content type or expected response format. - -```js -const data = await response.json(); -``` - -Send JSON with explicit headers. - -```js -await fetch("/api/users", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(user), -}); -``` - -Use `URL` and `URLSearchParams` for URLs. - -```js -const url = new URL("/api/search", location.origin); -url.searchParams.set("q", query); -``` - -Use `AbortController` to cancel requests. - -```js -const controller = new AbortController(); - -const response = await fetch(url, { - signal: controller.signal, -}); -``` - -Be careful with credentials. - -```js -fetch(url, { credentials: "include" }); -``` - -Understand CORS behavior rather than trying to bypass it client-side. - -Avoid putting secrets in browser JavaScript. - -Use exponential backoff or controlled retry logic for transient failures. - -Avoid retrying non-idempotent requests unless designed for it. - -## Browser Storage - -Use `localStorage` for small, non-sensitive persistent string data. - -```js -localStorage.setItem("theme", "dark"); -const theme = localStorage.getItem("theme"); -``` - -Use `sessionStorage` for tab/session-lifetime string data. - -Do not store sensitive tokens or secrets in localStorage if avoidable. - -Wrap storage access because it can throw in privacy modes or quota situations. - -```js -try { - localStorage.setItem("key", value); -} catch {} -``` - -Use IndexedDB for larger structured client-side data. - -Use Cache Storage for request/response caching, typically with service workers. - -Use cookies only when their HTTP behavior is needed. - -Set cookie security attributes server-side where possible: `HttpOnly`, `Secure`, `SameSite`. - -## Web Components - -Use custom elements when native component encapsulation and framework independence are useful. - -```js -class UserCard extends HTMLElement { - connectedCallback() { - this.textContent = "User"; - } -} - -customElements.define("user-card", UserCard); -``` - -Use shadow DOM for style and DOM encapsulation. - -```js -const root = this.attachShadow({ mode: "open" }); -``` - -Use templates for reusable markup. - -Use attributes for string-based configuration and properties for rich values. - -Clean up side effects in `disconnectedCallback`. - -Avoid web components when a project’s framework component model is clearly the better local fit. - -## Forms - -Use semantic form elements. - -Use `FormData` to read form values. - -```js -const formData = new FormData(form); -const email = formData.get("email"); -``` - -Use built-in constraint validation where appropriate. - -```js -if (!form.checkValidity()) { - form.reportValidity(); -} -``` - -Use proper `name` attributes. - -Use button `type` explicitly. - -```html -<button type="submit">Save</button> -<button type="button">Cancel</button> -``` - -Prevent default form submission only when handling submission in JavaScript. - -```js -form.addEventListener("submit", async (event) => { - event.preventDefault(); -}); -``` - -Do not rely only on client-side validation. Validate on the server too. - -## Accessibility Defaults - -Use semantic HTML first. - -Use buttons for actions and links for navigation. - -Do not replace native controls with custom ones unless necessary. - -Preserve keyboard access. - -Manage focus intentionally for dialogs, menus, and route changes. - -Use ARIA only when native HTML cannot express the behavior. - -Do not use ARIA to change semantics incorrectly. - -Keep accessible names clear. - -Use `aria-live` for dynamic status updates when needed. - -Respect reduced motion preferences. - -```js -const prefersReducedMotion = matchMedia("(prefers-reduced-motion: reduce)").matches; -``` - -Ensure JavaScript-enhanced experiences still fail gracefully where possible. - -## Internationalization - -Use `Intl.NumberFormat` for numbers, currency, and percentages. - -```js -const formatter = new Intl.NumberFormat(undefined, { - style: "currency", - currency: "USD", -}); -``` - -Use `Intl.DateTimeFormat` for dates. - -Use `Intl.RelativeTimeFormat` for relative time. - -```js -const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: "auto" }); -``` - -Use `Intl.PluralRules` for pluralization logic. - -Do not concatenate translated strings from fragments when grammar may vary. - -Avoid assuming English word order, decimal separators, currency position, or plural rules. - -## Performance - -Prefer clarity first, then optimize measured bottlenecks. - -Avoid unnecessary work in hot paths. - -Debounce frequent user input. - -```js -function debounce(fn, delay) { - let timeoutId; - - return (...args) => { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => fn(...args), delay); - }; -} -``` - -Throttle high-frequency events when appropriate. - -Use `requestAnimationFrame()` for animation work. - -Use `requestIdleCallback()` as progressive enhancement for non-urgent background work. - -Use `IntersectionObserver` for lazy visibility work. - -Use `ResizeObserver` for size changes. - -Avoid repeated DOM queries in tight loops when references can be reused. - -Avoid forced synchronous layout by interleaving DOM reads and writes. - -Use event delegation for many repeated elements. - -Use lazy loading and dynamic imports for large optional code. - -Use web workers for CPU-heavy work that would block the main thread. - -Use transferables for large binary data where appropriate. - -Prefer built-in browser APIs over large dependencies for simple tasks. - -Measure with browser performance tools before complex optimization. - -## Security - -Never trust client input. - -Escape or sanitize untrusted HTML. - -Prefer `textContent` over `innerHTML`. - -Avoid `eval()`, `new Function()`, and string-based timers. - -```js -setTimeout(callback, 100); -``` - -not: - -```js -setTimeout("callback()", 100); -``` - -Avoid inline scripts where Content Security Policy matters. - -Do not expose secrets in frontend code. - -Validate URLs before navigating or fetching if they come from untrusted input. - -Be careful with open redirects. - -Use `rel="noopener noreferrer"` for untrusted new-window links. - -Understand XSS, CSRF, CORS, clickjacking, and supply-chain risks. - -Use Subresource Integrity for third-party scripts where applicable. - -Keep dependencies updated. - -Minimize dependency surface. - -## Clean Code Principles - -Write code for readers. - -Use names that describe intent. - -```js -const activeSubscriptionCount = subscriptions.filter((s) => s.active).length; -``` - -Avoid vague names like `data`, `obj`, `tmp`, and `val` unless the scope is tiny and obvious. - -Keep functions focused. - -Avoid deeply nested control flow. - -Prefer explicit data flow over hidden mutation. - -Prefer simple conditionals over clever expressions. - -Do not over-abstract early. - -Remove dead code. - -Avoid comments that restate the code. - -Use comments to explain why, constraints, non-obvious tradeoffs, or external quirks. - -Keep related code close together. - -Separate pure computation from side effects. - -Make invalid states hard to represent where possible. - -Normalize data at boundaries. - -Avoid boolean traps. - -Prefer clear configuration objects. - -Handle edge cases deliberately. - -Fail loudly in developer-facing code when invariants are violated. - -Use consistent formatting. - -Let automated formatters handle style. - -## Type Awareness Without TypeScript - -Even in plain JavaScript: - -- Keep value shapes consistent. -- Avoid functions that return many unrelated types. -- Document complex object shapes with JSDoc if TypeScript is not used. -- Validate unknown inputs. -- Avoid excessive dynamic property access. -- Prefer predictable APIs. - -Example JSDoc: - -```js -/** - * @param {{ id: string, name: string }} user - * @returns {string} - */ -function getDisplayName(user) { - return user.name; -} -``` - -Use TypeScript for larger codebases when static checking would reduce risk. - -## API Design - -Design functions around clear contracts. - -Keep public APIs small. - -Prefer predictable return values. - -Avoid returning `null`, `undefined`, `false`, and throwing for similar failure modes in the same API. - -Use options objects for extensibility. - -```js -function search(query, { limit = 20, signal } = {}) {} -``` - -Use stable naming conventions. - -Use async APIs consistently when operations may become asynchronous. - -Avoid exposing internal mutable data. - -```js -getItems() { - return [...this.#items]; -} -``` - -Version APIs when consumers depend on them. - -## Data Validation - -Validate at trust boundaries: - -- User input. -- Network responses. -- Local storage. -- URL parameters. -- Messages from workers, frames, or extensions. -- Third-party library output. - -Use schema validation for complex external data. - -For simple validation, use clear checks. - -```js -function isUser(value) { - return ( - value !== null && - typeof value === "object" && - typeof value.id === "string" && - typeof value.name === "string" - ); -} -``` - -Avoid assuming JSON responses match expected shape. - -## URL And Navigation APIs - -Use `URL` for parsing and composing URLs. - -```js -const url = new URL(location.href); -const page = url.searchParams.get("page"); -``` - -Use `URLSearchParams`. - -```js -const params = new URLSearchParams({ q: query, page: "1" }); -``` - -Use History API for client-side navigation. - -```js -history.pushState({ page: "settings" }, "", "/settings"); -``` - -Listen for `popstate`. - -```js -window.addEventListener("popstate", handlePopState); -``` - -Use `location.assign()` for navigation when appropriate. - -Use `location.replace()` when the current page should not remain in history. - -## Timers And Scheduling - -Use `setTimeout()` for delayed work. - -Use `setInterval()` carefully and clear it when done. - -```js -const intervalId = setInterval(tick, 1000); -clearInterval(intervalId); -``` - -Prefer recursive `setTimeout()` when work duration matters. - -```js -async function poll() { - await refresh(); - setTimeout(poll, 5000); -} -``` - -Use `queueMicrotask()` for microtask scheduling. - -```js -queueMicrotask(() => { - notifyObservers(); -}); -``` - -Understand that promises schedule microtasks and timers schedule macrotasks. - -## Workers And Messaging - -Use web workers for CPU-heavy tasks. - -```js -const worker = new Worker(new URL("./worker.js", import.meta.url), { - type: "module", -}); -``` - -Use `postMessage()` for communication. - -Use structured clone-compatible data. - -Use transferables for large buffers. - -```js -worker.postMessage(buffer, [buffer]); -``` - -Terminate workers when no longer needed. - -```js -worker.terminate(); -``` - -Validate messages received from workers or other contexts. - -## Clipboard, Files, And Binary Data - -Use Async Clipboard API where available and permission-appropriate. - -```js -await navigator.clipboard.writeText(text); -``` - -Use `File`, `Blob`, and `FileReader` or modern blob methods. - -```js -const text = await file.text(); -const buffer = await file.arrayBuffer(); -``` - -Use object URLs for local previews and revoke them. - -```js -const url = URL.createObjectURL(file); -URL.revokeObjectURL(url); -``` - -Use streams for large data where appropriate. - -## Streams - -Use Web Streams for incremental data processing when useful. - -```js -const reader = response.body.getReader(); -``` - -Prefer higher-level APIs unless streaming is needed. - -Use `TextEncoder` and `TextDecoder`. - -```js -const decoder = new TextDecoder(); -const text = decoder.decode(buffer); -``` - -Use `ReadableStream`, `WritableStream`, and `TransformStream` for advanced streaming workflows. - -## Canvas, Media, And Graphics - -Use `<canvas>` for immediate-mode 2D drawing. - -Use `requestAnimationFrame()` for animation loops. - -```js -function frame() { - draw(); - requestAnimationFrame(frame); -} -``` - -Use `OffscreenCanvas` as progressive enhancement where supported. - -Use WebGL or WebGPU for advanced graphics when appropriate. - -Use media APIs such as `HTMLMediaElement`, `MediaStream`, and `MediaRecorder` with permission and compatibility handling. - -Always handle permissions and user gestures for media features. - -## Progressive Enhancement - -Feature-detect APIs before use. - -```js -if ("clipboard" in navigator) {} -if ("IntersectionObserver" in window) {} -``` - -Use fallback behavior. - -```js -if ("showOpenFilePicker" in window) { - // enhanced file picker -} else { - // input[type=file] -} -``` - -Avoid browser sniffing unless absolutely necessary. - -Load polyfills conditionally when practical. - -Enhance from semantic HTML and functional basics. - -Use CSS feature queries and JS feature checks for newer features. - -## Modern Cutting-Edge APIs Worth Considering With Fallbacks - -These can be recommended when they improve UX and have fallback paths: - -- `Temporal` for robust date/time when available or via polyfill. -- View Transitions API for progressive route/page transitions. -- File System Access API with file input fallback. -- Web Share API with clipboard or manual fallback. -- Async Clipboard API with selection/manual fallback. -- Compression Streams for browser-native compression where available. -- WebGPU for advanced graphics with WebGL/canvas fallback. -- OffscreenCanvas for worker-based rendering fallbacking to main-thread canvas. -- Navigation API for advanced client-side routing where supported. -- `scheduler.postTask()` for prioritized task scheduling where available. -- Popover API for native popovers with custom fallback. -- Declarative Shadow DOM when server-rendering web components. -- Import maps where supported or build-tool fallback. -- Speculation Rules API for progressive prerender/prefetch. -- Sanitizer API where available, with established sanitizer library fallback. -- Shared Storage, FedCM, and privacy-related APIs only for suitable use cases and with careful support checks. - -## Testing - -Write tests for behavior, not implementation details. - -Use unit tests for pure logic. - -Use integration tests for module interactions. - -Use end-to-end tests for critical user flows. - -Test edge cases and failure paths. - -Test async behavior deterministically. - -Avoid brittle tests based on timing where possible. - -Use fake timers when appropriate. - -Mock network boundaries deliberately. - -Do not over-mock the code under test. - -Use representative fixtures. - -Keep tests readable and maintainable. - -## Debugging - -Use browser DevTools. - -Use breakpoints instead of excessive logging for complex issues. - -Use `console.log`, `console.warn`, `console.error`, `console.table`, and `console.time` intentionally. - -Remove noisy debug logging before production. - -Preserve useful operational logging where appropriate. - -Use source maps in development and production error reporting where safe. - -Inspect network requests, performance profiles, layout shifts, memory, and event listeners. - -## Tooling Defaults - -Use a formatter such as Prettier for style consistency. - -Use a linter such as ESLint for bug-prone patterns. - -Use TypeScript or JSDoc checking for larger projects. - -Use modern bundlers only when needed for dependencies, transforms, optimization, or developer experience. - -Prefer native ESM where practical. - -Use package lockfiles. - -Use npm scripts or equivalent task runners for common commands. - -Keep dependencies minimal and justified. - -Audit dependency health before adding packages. - -## Dependency Use - -Prefer platform APIs for standard capabilities. - -Add dependencies when they provide meaningful value: - -- Complex date/time manipulation. -- Schema validation. -- Internationalization frameworks. -- Rich UI components. -- State management at scale. -- Parsing. -- Cryptography wrappers around native primitives. -- Specialized algorithms. - -Avoid dependencies for trivial utilities. - -Check bundle size, maintenance, security, license, and API stability. - -Prefer tree-shakeable packages. - -Avoid importing entire libraries for one small function. - -## Browser Compatibility - -Use broadly shipped features by default when targeting modern browsers. - -Use transpilation/polyfills only according to project browser support policy. - -Understand the difference between syntax transforms and runtime polyfills. - -Feature-detect runtime APIs. - -Avoid assuming all embedded browsers are current. - -Check compatibility for APIs that are newer, mobile-specific, permission-gated, or behind secure-context requirements. - -Remember many modern APIs require HTTPS. - -## Node-Compatible JavaScript - -When writing JavaScript that may run in Node too: - -- Use ESM or CommonJS consistently. -- Prefer standard Web APIs available in modern Node where appropriate. -- Use `node:` specifiers for built-in modules. - -```js -import fs from "node:fs/promises"; -``` - -- Avoid browser globals unless guarded. -- Avoid Node globals in browser-targeted code. -- Keep environment-specific code isolated. -- Use `process.env` only in server/build contexts. -- Do not leak server secrets into browser bundles. - -## Common Pitfalls - -Avoid accidental assignment in conditionals. - -```js -if (value === expected) {} -``` - -Avoid comparing objects by value with `===`. - -```js -{} === {}; // false -``` - -Avoid mutating state in place when consumers expect immutability. - -Avoid stale closures in async callbacks and UI code. - -Avoid unhandled promise rejections. - -Avoid forgetting `return` in block-bodied arrow functions. - -```js -items.map((item) => { - return item.id; -}); -``` - -Avoid using `forEach` when you need `break`, `continue`, or `await`. - -Avoid relying on object key order for core logic. - -Avoid using array indexes as persistent IDs. - -Avoid parsing numbers without validating the result. - -Avoid using `Date` parsing for non-ISO strings. - -Avoid assuming `fetch` rejects on 404 or 500. - -Avoid using `innerHTML` with untrusted data. - -Avoid memory leaks from lingering timers, observers, subscriptions, workers, or event listeners. - -Avoid excessive abstraction. - -Avoid clever one-liners that hide control flow or error handling. - -## Default Style Preferences - -Use clear, boring code. - -Prefer this: - -```js -function getActiveUserNames(users) { - return users - .filter((user) => user.active) - .map((user) => user.name); -} -``` - -Over this: - -```js -const getActiveUserNames = (u) => u.filter((x) => x.active).map((x) => x.name); -``` - -Prefer guard clauses. - -Prefer named intermediate values when they clarify intent. - -```js -const hasValidEmail = email.includes("@") && email.includes("."); -if (!hasValidEmail) return; -``` - -Avoid compressing too much logic into a single expression. - -Use descriptive errors. - -Keep side effects visible. - -Prefer explicit imports. - -Prefer stable APIs over experimental APIs unless progressive enhancement is clear. - -## Summary Of Common-Knowledge Defaults - -I would generally consider the following redundant in a project-specific JavaScript guide unless the project needs stricter local policy: - -- Use `const` / `let` instead of `var`. -- Use modules. -- Use strict equality. -- Use template literals. -- Use destructuring, spread, optional chaining, and nullish coalescing. -- Use promises and `async` / `await`. -- Use `fetch` with `response.ok` checks. -- Use `Map` / `Set` when appropriate. -- Use modern array methods. -- Avoid mutation unless intentional. -- Prefer semantic DOM APIs. -- Use `textContent` for untrusted text. -- Avoid `eval`. -- Validate external input. -- Prefer small functions and clear names. -- Avoid premature abstraction. -- Use feature detection for newer browser APIs. -- Use `Intl` for user-facing formatting. -- Use `AbortController` for cancellable async work. -- Use observers instead of polling. -- Use standard browser APIs before dependencies. -- Keep code readable, explicit, and maintainable. diff --git a/.agents/skills/project-discipline-guides/mirrors/javascript/gemini_mirror.md b/.agents/skills/project-discipline-guides/mirrors/javascript/gemini_mirror.md deleted file mode 100644 index 0cf817334..000000000 --- a/.agents/skills/project-discipline-guides/mirrors/javascript/gemini_mirror.md +++ /dev/null @@ -1,91 +0,0 @@ -# JavaScript Knowledge Mirror: Standard Best Practices & APIs - -This guide represents a baseline of modern JavaScript knowledge, covering syntax, standard library features, and clean code principles. - -## 1. Variables and Scope - -* **`const` and `let`**: Use `const` by default for all variable declarations. Use `let` only when the variable must be reassigned. Never use `var`. -* **Block Scope**: Understand that `let` and `const` are block-scoped, preventing hoisting issues and accidental global leakage. -* **Global Avoidance**: Minimize the use of the global scope. Wrap code in modules or functions. - -## 2. Functions and Modern Syntax - -* **Arrow Functions**: Use arrow functions (`() => {}`) for short callbacks and when you need to preserve the lexical `this` context. -* **Function Declarations**: Use `function` declarations for top-level logic or when hoisting is intentionally required (rare). -* **Default Parameters**: Use default values in function signatures (`function fn(a = 10) {}`) instead of conditional checks inside the body. -* **Rest Parameters**: Use `...args` to handle an indefinite number of arguments as an array. -* **Destructuring**: - * **Objects**: `const { name, age } = user;` - * **Arrays**: `const [first, second] = list;` - * **Function Arguments**: `function print({ id, label }) {}` -* **Spread Operator**: Use `...` for shallow copying objects/arrays or merging them (`{ ...defaults, ...overrides }`). -* **Template Literals**: Use backticks for string interpolation and multi-line strings: `` `Value: ${val}` ``. - -## 3. Objects and Arrays - -* **Shorthand Properties**: Use `{ name }` instead of `{ name: name }`. -* **Computed Property Names**: Use `{[key]: value}` for dynamic keys. -* **Optional Chaining (`?.`)**: Safely access deeply nested properties: `user?.profile?.email`. -* **Nullish Coalescing (`??`)**: Use `??` to provide fallback values for `null` or `undefined`, avoiding the pitfalls of `||` with falsy values like `0` or `""`. -* **Array Methods**: - * Iteration: `forEach` (side effects), `map` (transformation). - * Filtering/Searching: `filter`, `find`, `findIndex`, `some`, `every`, `includes`. - * Reducing: `reduce` (for complex aggregations). - * Flattening: `flat()`, `flatMap()`. - * Access: `.at(-1)` for the last element. -* **Object Methods**: `Object.keys()`, `Object.values()`, `Object.entries()`, and `Object.fromEntries()`. -* **`Object.groupBy()`**: (Modern) Use for categorizing items in an array. - -## 4. Asynchronous Programming - -* **Promises**: Use Promises for all asynchronous operations. -* **`async/await`**: Use `async`/`await` for cleaner, more readable asynchronous code. Always wrap in `try/catch` for error handling. -* **Promise Concurrency**: - * `Promise.all()`: Fails fast if any promise rejects. - * `Promise.allSettled()`: Waits for all to finish, regardless of outcome. - * `Promise.any()`: Returns the first successful promise. - * `Promise.race()`: Returns the result of the first settled promise (resolve or reject). -* **`AbortController`**: Use `AbortController` and `AbortSignal` to cancel `fetch` requests or other async tasks (e.g., timeouts). - -## 5. Classes and Modules - -* **ES Modules (ESM)**: Use `import` and `export`. Prefer named exports for better tooling support, use `default` export sparingly. -* **Dynamic Imports**: Use `import()` for code-splitting and lazy-loading. -* **Class Syntax**: Use the `class` keyword. - * **Private Fields**: Use the `#` prefix for truly private class members: `#internalState`. - * **Static Blocks**: Use `static {}` for complex static initialization. - * **Inheritance**: Use `extends` and `super()`. - -## 6. Standard Web APIs - -* **Fetch API**: Use `fetch()` for network requests. Remember it doesn't reject on 4xx/5xx errors; check `response.ok`. -* **URL and URLSearchParams**: Use the `URL` API to parse and manipulate URLs and query parameters instead of string manipulation. -* **`structuredClone()`**: Use for deep cloning objects (native alternative to `JSON.parse(JSON.stringify())` or Lodash). -* **Intl API**: Use `Intl.NumberFormat`, `Intl.DateTimeFormat`, and `Intl.RelativeTimeFormat` for localization. -* **DOM Manipulation**: - * Use `querySelector` and `querySelectorAll`. - * Use `classList` (`add`, `remove`, `toggle`, `contains`) for CSS classes. - * Use `dataset` for `data-*` attributes. - * Use `addEventListener` with options like `{ once: true }` or `{ signal }`. -* **Intersection Observer**: Use for lazy-loading or scroll-triggered animations. -* **Resize Observer**: Use for responding to element size changes. - -## 7. Error Handling and Debugging - -* **Custom Errors**: Extend the `Error` class for domain-specific errors. -* **Error Cause**: Use the `cause` property when re-throwing errors to maintain the stack trace: `new Error("Failed", { cause: originalErr })`. -* **`console` methods**: Beyond `log`, use `warn`, `error`, `table`, `group/groupEnd`, and `time/timeEnd`. - -## 8. Clean Code and Best Practices - -* **Naming**: - * `camelCase` for variables and functions. - * `PascalCase` for classes and components. - * `SCREAMING_SNAKE_CASE` for constants. - * Use descriptive, verb-based names for functions (e.g., `getUserData`, `isEmailValid`). -* **Early Returns**: Use guard clauses to exit functions early, reducing nesting. -* **Immutability**: Avoid mutating state or function arguments. Return new objects/arrays instead. -* **Pure Functions**: Aim for functions with no side effects that return the same output for the same input. -* **Avoid Magic Numbers**: Extract literals to named constants. -* **Module Size**: Keep modules focused (Single Responsibility Principle). -* **Comments**: Use comments to explain *why* something is done, not *what* is being done (the code should be self-documenting). diff --git a/.agents/skills/project-discipline-guides/mirrors/javascript/mirror.md b/.agents/skills/project-discipline-guides/mirrors/javascript/mirror.md deleted file mode 100644 index c852b6d46..000000000 --- a/.agents/skills/project-discipline-guides/mirrors/javascript/mirror.md +++ /dev/null @@ -1,248 +0,0 @@ -# JavaScript Unified Lowest Common Denominator (LCD) Mirror - -This document represents the intersection of standard JavaScript knowledge and best practices consistently evidenced across three independent source mirrors. - -## 1. Variables and Scope - -* **Declaration Strategy**: Use `const` by default for all variables. Use `let` only when reassignment is explicitly required. -* **Avoid `var`**: Do not use `var` due to its function-scoping and hoisting behaviors. -* **Block Scoping**: Leverage the block-scoped nature of `let` and `const` to prevent accidental global leakage and hoisting issues. - -## 2. Functions and Modern Syntax - -* **Arrow Functions**: Use arrow functions (`() => {}`) for callbacks and to preserve the lexical `this` context. -* **Function Declarations**: Use the `function` keyword for top-level logic or when hoisting is intentionally required. -* **Default Parameters**: Assign default values in function signatures (`param = value`) to handle missing arguments. -* **Rest Parameters**: Use the rest syntax (`...args`) to capture an indefinite number of arguments as an array. -* **Destructuring**: Use destructuring to extract values from objects and arrays: - * `const { key } = object;` - * `const [first] = array;` -* **Spread Operator**: Use the `...` syntax for shallow copying or merging objects and arrays. -* **Template Literals**: Use backticks for string interpolation (`${value}`) and creating multi-line strings. - -## 3. Objects and Arrays - -* **Property Enhancements**: - * **Shorthand Properties**: Use `{ name }` when the key and variable name match. - * **Computed Property Names**: Use `{[key]: value}` for dynamic property keys. -* **Safety Operators**: - * **Optional Chaining (`?.`)**: Safely access nested properties (e.g., `user?.profile?.id`). - * **Nullish Coalescing (`??`)**: Provide fallback values specifically for `null` or `undefined`. -* **Standard Methods**: - * **Object Methods**: `Object.keys()`, `Object.values()`, `Object.entries()`, and `Object.fromEntries()`. - * **Array Iteration/Transformation**: `forEach`, `map`, `filter`, `reduce`. - * **Array Searching**: `find`, `findIndex`, `some`, `every`, `includes`. - * **Array Utility**: `flat()`, `flatMap()`, and `at()` (for relative/negative indexing). - -## 4. Asynchronous Programming - -* **Promises**: Use Promises for managing all asynchronous operations. -* **Async/Await**: Use `async`/`await` for readable asynchronous control flow. -* **Error Handling**: Always wrap asynchronous logic in `try/catch` blocks. -* **Promise Concurrency**: - * `Promise.all()`: Fails fast if any promise rejects. - * `Promise.allSettled()`: Waits for all promises to finish regardless of outcome. - * `Promise.any()`: Returns the first fulfilled promise. - * `Promise.race()`: Returns the first settled promise (resolve or reject). - -## 5. Classes and Modules - -* **ES Modules (ESM)**: Use `import` and `export` statements for modularity. -* **Class Syntax**: Use the `class` keyword for stateful entities. - * **Private Fields**: Use the `#` prefix for private class members (e.g., `#state`). - * **Inheritance**: Use `extends` and `super()` for class-based inheritance. - -## 6. Web APIs - -* **Fetch API**: Use `fetch()` for network requests. Always check `response.ok` as `fetch` does not reject on HTTP error statuses (4xx/5xx). -* **AbortController**: Use `AbortController` and `AbortSignal` to cancel asynchronous tasks like network requests. -* **URL API**: Use `URL` and `URLSearchParams` to manipulate URLs and query strings. -* **DOM Manipulation**: - * Selection: `querySelector` and `querySelectorAll`. - * Attributes/Classes: `classList` (add, remove, toggle) and `dataset`. - * Events: `addEventListener` (including options like `{ once: true }` or `{ signal }`). -* **Deep Cloning**: Use `structuredClone()` for deep copies of objects. -* **Observers**: Use `IntersectionObserver` and `ResizeObserver` for responding to layout and visibility changes. -* **Intl API**: Use the `Intl` namespace (e.g., `DateTimeFormat`, `NumberFormat`) for localized formatting. - -## 7. Error Handling and Debugging - -* **Custom Errors**: Extend the `Error` class to create domain-specific error types. -* **Error Cause**: Use the `cause` property when re-throwing to preserve original stack traces: `new Error("msg", { cause: err })`. -* **Console Methods**: Utilize `log`, `warn`, `error`, `table`, `group`, and `time` for development and debugging. - -## 8. Clean Code Principles - -* **Descriptive Naming**: Use clear, intention-revealing names for variables and functions. -* **Early Returns**: Use guard clauses to handle edge cases early and reduce function nesting. -* **Immutability**: Avoid mutating objects, arrays, or function arguments; return new instances instead. -* **Pure Functions**: Prioritize functions that return consistent outputs for given inputs without side effects. -* **Comments**: Use comments to explain *why* complex logic exists, rather than *what* the code is doing. - -# JavaScript Unified Lowest Common Denominator (LCD) Mirror (Revised) - -This document represents the absolute intersection of JavaScript knowledge consistently evidenced across the Gemini, Claude, and Codex source mirrors. It strictly excludes any feature, API, or best practice missing from any one of the source documents. - -## 1. Variables and Scope - -* **Declaration Strategy**: Use `const` by default for variables. Use `let` only when the variable must be reassigned. -* **Avoid `var`**: Do not use `var` for variable declarations. -* **Scoped Declarations**: Understand that `let` and `const` provide block-scoped declarations, which prevents accidental global leakage and issues associated with hoisting. - -## 2. Functions and Modern Syntax - -* **Arrow Functions**: Use arrow functions (`() => {}`) for callbacks and when preserving the lexical `this` context is required. -* **Function Declarations**: Use traditional `function` declarations for top-level logic or when hoisting is required. -* **Default Parameters**: Use default values in function signatures (`param = value`) to handle missing arguments. -* **Rest Parameters**: Use the rest syntax (`...args`) to capture an indefinite number of arguments into a single array. -* **Destructuring**: Use destructuring to extract data from objects and arrays: - * `const { key } = object;` - * `const [first] = array;` - * Destructuring is also applicable to function parameters. -* **Spread Operator (Arrays/Objects)**: Use the spread syntax (`...`) for shallow copying or merging objects and arrays. -* **Template Literals**: Use backticks for string interpolation (`${value}`) and for defining multi-line strings. - -## 3. Objects and Arrays - -* **Object Literals**: - * **Shorthand Properties**: Use `{ key }` when the variable name matches the property key. - * **Computed Property Names**: Use `{[key]: value}` for dynamic property keys. -* **Safe Access and Defaults**: - * **Optional Chaining (`?.`)**: Safely access deeply nested properties (e.g., `user?.address?.city`). - * **Nullish Coalescing (`??`)**: Provide fallback values specifically for `null` or `undefined` inputs. -* **Core Methods**: - * **Object Static Methods**: `Object.keys()`, `Object.values()`, `Object.entries()`, and `Object.fromEntries()`. - * **Array Transformation/Iteration**: `map`, `filter`, `forEach`, `reduce`. - * **Array Searching**: `find`, `findIndex`, `some`, `every`, `includes`. - * **Array Utility**: `flat()`, `flatMap()`, and `at()` (for relative indexing, such as the last element). - -## 4. Asynchronous Programming - -* **Promises**: Use Promises for all asynchronous operations. -* **Async/Await**: Use `async`/`await` for readable asynchronous control flow. -* **Error Handling**: Wrap asynchronous logic in `try/catch` blocks to handle failures. -* **Promise Combinators**: - * `Promise.all()`: Continues only if all promises fulfill. - * `Promise.allSettled()`: Waits for all promises to finish regardless of outcome. - * `Promise.any()`: Continues as soon as the first promise fulfills. - * `Promise.race()`: Continues as soon as the first promise settles (fulfills or rejects). - -## 5. Classes and Modules - -* **ES Modules (ESM)**: Use `import` and `export` statements. Named exports are preferred, while default exports should be used when a module has a primary concept. -* **Dynamic Imports**: Use `import()` for lazy-loading or code-splitting. -* **Class Syntax**: Use the `class` keyword for defining stateful entities. - * **Private Fields**: Use the `#` prefix (e.g., `#field`) for truly private class members. - * **Inheritance**: Use `extends` and `super()` to implement class inheritance. - -## 6. Standard Web APIs - -* **Fetch API**: Use `fetch()` for network requests. Always check `response.ok` because `fetch` only rejects on network failure, not on HTTP error status codes (4xx/5xx). -* **AbortController**: Use `AbortController` and `AbortSignal` to cancel asynchronous tasks such as network requests. -* **URL API**: Use the `URL` and `URLSearchParams` objects to parse and manipulate URLs and query parameters. -* **Deep Cloning**: Use `structuredClone()` for deep cloning objects. -* **Internationalization (Intl)**: Use the `Intl` API (e.g., `DateTimeFormat`, `NumberFormat`) for locale-aware formatting. -* **DOM Selection and Interaction**: - * Use `querySelector` and `querySelectorAll` for element selection. - * Use `classList` (add, remove, toggle) for CSS class management. - * Use `dataset` for accessing `data-*` attributes. - * Use `addEventListener` for event handling (including options like `once` or `signal`). -* **Observers**: Use `IntersectionObserver` and `ResizeObserver` for responding to element visibility and size changes. - -## 7. Error Handling and Debugging - -* **Custom Errors**: Extend the `Error` class to create specific error types. -* **Error Cause**: Use the `cause` property when throwing errors to maintain a chain of responsibility: `new Error("message", { cause: originalError })`. -* **Console Methods**: Use `console.log`, `console.warn`, `console.error`, `console.table`, and `console.time`/`timeEnd`. - -## 8. Clean Code and Best Practices - -* **Intention-Revealing Naming**: Use names that clearly describe the intent of variables and functions. Avoid vague names. -* **Early Returns**: Use guard clauses to handle edge cases early and reduce the depth of nested logic. -* **Immutability**: Avoid mutating objects, arrays, or function arguments. Return new data structures instead. -* **Pure Functions**: Aim for functions that produce the same output for the same input and have no side effects. -* **Magic Numbers**: Extract literal values into named constants for better readability. -* **Single Responsibility**: Keep modules and functions focused on a single responsibility. -* **Comments**: Use comments to explain *why* a piece of code exists or follows a specific pattern, rather than *what* the code is doing. - -# JavaScript Unified Lowest Common Denominator (LCD) Mirror (Final) - -This document represents the absolute literal intersection of the Gemini, Claude, and Codex mirrors. Only features and guidelines explicitly evidenced in all three sources are included. - -## 1. Variables and Scope - -* **Declarations**: Use `const` by default. Use `let` only when reassignment is necessary. -* **Avoid `var`**: Do not use `var`. -* **Block Scope**: Utilize `let` and `const` for block-scoped declarations to prevent global scope leakage and issues with hoisting. - -## 2. Functions and Modern Syntax - -* **Function Types**: - * Use arrow functions (`() => {}`) for callbacks and to preserve lexical `this`. - * Use `function` declarations for top-level logic or when hoisting is needed. -* **Parameters**: - * **Default Parameters**: Use `param = value` in signatures to handle missing arguments. - * **Rest Parameters**: Use `...args` to capture multiple arguments as an array. -* **Destructuring**: Use destructuring to extract values from objects and arrays (applicable to variables and function parameters). -* **Spread Operator**: Use `...` for shallow copying and merging of objects and arrays. -* **Template Literals**: Use backticks for string interpolation (`${value}`) and multi-line strings. - -## 3. Objects and Arrays - -* **Object Literals**: - * **Shorthand Properties**: Use `{ key }` when the variable name matches the property name. - * **Computed Property Names**: Use `{[key]: value}` for dynamic keys. -* **Safety and Defaults**: - * **Optional Chaining (`?.`)**: Safely access nested properties. - * **Nullish Coalescing (`??`)**: Provide fallbacks for `null` or `undefined`. -* **Static Object Methods**: `Object.keys()`, `Object.values()`, `Object.entries()`, and `Object.fromEntries()`. -* **Array Methods**: - * **Iteration/Transformation**: `forEach`, `map`, `filter`, `reduce`. - * **Searching/Validation**: `find`, `findIndex`, `some`, `every`, `includes`. - * **Utility**: `flat()`, `flatMap()`, and `at()` (for relative indexing). - -## 4. Asynchronous Programming - -* **Execution**: Use `async`/`await` and Promises for asynchronous control flow. -* **Error Handling**: Use `try/catch` blocks to manage asynchronous failures. -* **Promise Combinators**: - * `Promise.all()`: Continues if all fulfill. - * `Promise.allSettled()`: Waits for all to finish regardless of outcome. - * `Promise.any()`: Returns the first fulfilled promise. - * `Promise.race()`: Returns the first settled promise. - -## 5. Classes and Modules - -* **ES Modules**: Use `import` and `export`. Prefer named exports. -* **Dynamic Imports**: Use `import()` for lazy loading. -* **Class Syntax**: Use the `class` keyword. - * **Private Fields**: Use the `#` prefix for private members. - * **Inheritance**: Use `extends` and `super()`. - -## 6. Standard Web APIs - -* **Networking**: Use `fetch()`. Always check `response.ok` (it does not reject on 4xx/5xx). -* **Cancellation**: Use `AbortController` and `AbortSignal`. -* **URLs**: Use `URL` and `URLSearchParams` for manipulation. -* **Cloning**: Use `structuredClone()` for deep copies. -* **Internationalization (Intl)**: Use `Intl` for locale-aware formatting (specifically `DateTimeFormat`, `NumberFormat`, and `RelativeTimeFormat`). -* **DOM**: - * Selection: `querySelector` and `querySelectorAll`. - * Attributes: `classList` and `dataset`. - * Events: `addEventListener` (including options like `once` and `signal`). -* **Observers**: Use `IntersectionObserver` and `ResizeObserver`. - -## 7. Error Handling and Debugging - -* **Errors**: Extend the `Error` class for custom types and use the `cause` property for re-throwing. -* **Console**: Use `console.log` and `console.error`. - -## 8. Clean Code Principles - -* **Naming**: Use descriptive, intention-revealing names. -* **Flow Control**: Use guard clauses (early returns) to reduce nesting. -* **Immutability**: Prefer non-mutating operations; avoid mutating arguments. -* **Functions**: Aim for pure functions and adhere to the Single Responsibility Principle. -* **Constants**: Replace literal values (magic numbers) with named constants. -* **Documentation**: Use comments to explain *why* something is done, not *what* is done. - diff --git a/.agents/skills/project-discipline-guides/mirrors/performance/claude_mirror.md b/.agents/skills/project-discipline-guides/mirrors/performance/claude_mirror.md new file mode 100644 index 000000000..c9f3fe97a --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/performance/claude_mirror.md @@ -0,0 +1,509 @@ +# Web Performance: Common Knowledge Guide + +## 1. Core Web Vitals & Metrics + +### Key Metrics +- **LCP (Largest Contentful Paint)**: Measures loading performance. Target: ≤2.5s. Tracks the render time of the largest image/text block visible in the viewport. +- **INP (Interaction to Next Paint)**: Replaced FID in March 2024. Measures responsiveness across all interactions. Target: ≤200ms. +- **CLS (Cumulative Layout Shift)**: Measures visual stability. Target: ≤0.1. +- **FCP (First Contentful Paint)**: Time until first DOM content renders. Target: ≤1.8s. +- **TTFB (Time to First Byte)**: Server response time. Target: ≤800ms. +- **TBT (Total Blocking Time)**: Lab metric correlating with INP. Sum of blocking time on the main thread. + +### Measurement APIs +```js +// Use PerformanceObserver, not deprecated performance.timing +new PerformanceObserver((list) => { + for (const entry of list.getEntries()) { + console.log(entry.name, entry.startTime, entry.duration); + } +}).observe({ type: 'largest-contentful-paint', buffered: true }); + +// Long Animation Frames API (LoAF) - replaces Long Tasks for INP debugging +new PerformanceObserver((list) => { + for (const entry of list.getEntries()) { + console.log('LoAF:', entry.duration, entry.scripts); + } +}).observe({ type: 'long-animation-frame', buffered: true }); + +// Event Timing API for INP +new PerformanceObserver((list) => { + for (const entry of list.getEntries()) { + if (entry.interactionId) { + console.log(entry.name, entry.duration); + } + } +}).observe({ type: 'event', buffered: true, durationThreshold: 16 }); + +// Navigation Timing Level 2 +const nav = performance.getEntriesByType('navigation')[0]; +console.log(nav.domContentLoadedEventEnd - nav.startTime); + +// Resource Timing +performance.getEntriesByType('resource').forEach(r => { + console.log(r.name, r.transferSize, r.duration); +}); +``` + +### Use the `web-vitals` library +The standard for collecting field data; use the attribution build for debugging. + +```js +import { onLCP, onINP, onCLS } from 'web-vitals/attribution'; +onLCP(console.log); +onINP(console.log); +onCLS(console.log); +``` + +## 2. Loading & Resource Hints + +### Resource Hints +```html +<!-- Preconnect: establish early connections to critical origins --> +<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + +<!-- DNS Prefetch: lighter-weight fallback for preconnect --> +<link rel="dns-prefetch" href="https://example.com"> + +<!-- Preload: high-priority fetch for critical resources --> +<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin> +<link rel="preload" href="/hero.jpg" as="image" fetchpriority="high"> + +<!-- Modulepreload: preload ES modules --> +<link rel="modulepreload" href="/app.js"> + +<!-- Prefetch: low-priority fetch for next-navigation resources --> +<link rel="prefetch" href="/next-page.html"> +``` + +### Speculation Rules API (Chromium) +```html +<script type="speculationrules"> +{ + "prerender": [{"where": {"href_matches": "/*"}, "eagerness": "moderate"}], + "prefetch": [{"where": {"href_matches": "/*"}, "eagerness": "conservative"}] +} +</script> +``` + +### Priority Hints +```html +<img src="hero.jpg" fetchpriority="high" alt=""> +<img src="below-fold.jpg" fetchpriority="low" loading="lazy" alt=""> +<script src="analytics.js" fetchpriority="low" async></script> +``` + +### Script Loading +- `<script defer>`: Downloads in parallel, executes after HTML parsing in order. +- `<script async>`: Downloads in parallel, executes ASAP, no order guarantee. +- `<script type="module">`: Defers by default. +- Avoid synchronous render-blocking scripts in `<head>`. + +## 3. Images & Media + +### Modern Formats +- **AVIF**: Best compression, broad support since 2024. +- **WebP**: Universal support, fallback for AVIF. +- Use `<picture>` for format negotiation: + +```html +<picture> + <source srcset="hero.avif" type="image/avif"> + <source srcset="hero.webp" type="image/webp"> + <img src="hero.jpg" alt="" width="1200" height="600" loading="lazy" decoding="async"> +</picture> +``` + +### Responsive Images +```html +<img + srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1600.jpg 1600w" + sizes="(max-width: 600px) 100vw, 50vw" + src="hero-800.jpg" + alt="" + width="1600" + height="900" + loading="lazy" + decoding="async"> +``` + +### Required Image Attributes +- **Always set `width` and `height`** (or `aspect-ratio` in CSS) to prevent CLS. +- Use `loading="lazy"` for below-the-fold images. +- Use `loading="eager"` and `fetchpriority="high"` for LCP images. **Never lazy-load the LCP image.** +- Use `decoding="async"` to avoid blocking rendering. + +### Video +```html +<video + preload="metadata" + poster="poster.jpg" + playsinline + muted + loop> + <source src="clip.webm" type="video/webm"> + <source src="clip.mp4" type="video/mp4"> +</video> +``` +- Use `preload="none"` or `preload="metadata"` to avoid wasted bandwidth. +- Prefer AV1 > VP9 > H.264 for compression. + +## 4. Fonts + +### Best Practices +```css +@font-face { + font-family: 'Inter'; + src: url('/fonts/inter.woff2') format('woff2'); + font-weight: 100 900; /* variable font range */ + font-display: swap; /* or optional, fallback */ + unicode-range: U+0000-00FF; + size-adjust: 100%; + ascent-override: 90%; + descent-override: 22%; + line-gap-override: 0%; +} +``` + +- Use **WOFF2** exclusively (universal support). +- Use **variable fonts** for weight/style variation. +- `font-display: swap` for visibility (best LCP) or `optional` for stability (best CLS). +- **Preload critical fonts** with `crossorigin`. +- Use `size-adjust` and `*-override` to match fallback metrics, eliminating CLS from font swap. +- Subset fonts with `unicode-range` to reduce file size. + +### System Font Stack +```css +font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; +``` + +## 5. CSS Performance + +### Critical CSS +- Inline critical above-the-fold CSS in `<head>`. +- Defer non-critical CSS: +```html +<link rel="preload" href="non-critical.css" as="style" onload="this.rel='stylesheet'"> +``` + +### Modern CSS Features +- **`content-visibility: auto`**: Skip rendering work for off-screen content. +```css +.section { content-visibility: auto; contain-intrinsic-size: auto 500px; } +``` +- **`contain`**: Isolate subtrees to limit layout/paint scope. +```css +.widget { contain: layout style paint; } +``` +- **`will-change`**: Hint to the browser; use sparingly and remove after the animation. +- **`transform` / `opacity`** for animations (compositor-only, no layout/paint). +- Avoid animating `width`, `height`, `top`, `left` — they trigger layout. + +### Selector Performance +- Modern engines make selector cost mostly negligible; readability beats micro-optimization. +- Prefer flat class-based selectors over deep descendant chains. +- `:has()` is widely supported; use thoughtfully (it's more expensive than other selectors). + +### Layout +- Use **CSS Grid** and **Flexbox** instead of float/positioning hacks. +- Use **`aspect-ratio`** to reserve space: +```css +img { aspect-ratio: 16 / 9; width: 100%; height: auto; } +``` +- Use **container queries** (`@container`) for component-driven responsive design. + +## 6. JavaScript Performance + +### Code Splitting +```js +// Dynamic imports for route/component-level splitting +const module = await import('./heavy-feature.js'); + +// Lazy load on interaction +button.addEventListener('click', async () => { + const { feature } = await import('./feature.js'); + feature(); +}, { once: true }); +``` + +### Defer Non-Critical Work +```js +// scheduler.postTask (Chromium) - prefer over setTimeout(0) +scheduler.postTask(() => doWork(), { priority: 'background' }); + +// scheduler.yield() to break up long tasks +async function processItems(items) { + for (const item of items) { + process(item); + if (navigator.scheduling?.isInputPending?.()) { + await scheduler.yield(); + } + } +} + +// requestIdleCallback fallback +requestIdleCallback(() => analytics.send(), { timeout: 2000 }); +``` + +### Avoid Long Tasks +- Break tasks ≤50ms. +- Use `scheduler.yield()` or `await new Promise(r => setTimeout(r, 0))` to yield. +- Move CPU-heavy work to **Web Workers**. + +### Web Workers +```js +const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' }); +worker.postMessage(data); +worker.onmessage = (e) => handleResult(e.data); + +// Transfer ownership for zero-copy +worker.postMessage(buffer, [buffer]); +``` + +### Debounce / Throttle +```js +// Debounce +const debounce = (fn, ms) => { + let id; + return (...args) => { + clearTimeout(id); + id = setTimeout(() => fn(...args), ms); + }; +}; + +// Throttle with rAF for scroll/resize +let rafId; +window.addEventListener('scroll', () => { + if (rafId) return; + rafId = requestAnimationFrame(() => { + handleScroll(); + rafId = null; + }); +}, { passive: true }); +``` + +### Passive Event Listeners +```js +// Always use passive for scroll-blocking events +el.addEventListener('touchstart', handler, { passive: true }); +el.addEventListener('wheel', handler, { passive: true }); +``` + +### Modern Syntax & APIs +- Use `Map`/`Set` for keyed lookups and uniqueness. +- Use `structuredClone()` instead of `JSON.parse(JSON.stringify(x))`. +- Use `AbortController` to cancel fetches and event listeners: +```js +const controller = new AbortController(); +fetch(url, { signal: controller.signal }); +el.addEventListener('click', handler, { signal: controller.signal }); +controller.abort(); +``` +- Use `Object.groupBy` / `Map.groupBy` for grouping (2024+). +- Use `Array.prototype.at(-1)` instead of `arr[arr.length - 1]`. +- Use `String.prototype.replaceAll`. +- Use top-level `await` in modules. + +## 7. Network + +### HTTP & Caching +- **HTTP/2 or HTTP/3** for multiplexing; HTTP/3 (QUIC) preferred where available. +- **Brotli** compression for text assets; **Zstandard (zstd)** where supported. +- Long-term caching with content hashes: +``` +Cache-Control: public, max-age=31536000, immutable +``` +- Short cache for HTML: +``` +Cache-Control: no-cache +``` +- Use `stale-while-revalidate` for balance: +``` +Cache-Control: max-age=60, stale-while-revalidate=86400 +``` + +### Fetch API +```js +const res = await fetch(url, { + signal: AbortSignal.timeout(5000), + priority: 'low', // 'high' | 'low' | 'auto' + cache: 'force-cache', + keepalive: true, // for beacon-like requests +}); +``` + +### Streaming +```js +// Process large responses without buffering +const response = await fetch(url); +const reader = response.body.getReader(); +while (true) { + const { value, done } = await reader.read(); + if (done) break; + process(value); +} +``` + +### Service Workers +- Use for offline support, runtime caching, and request orchestration. +- Use **Workbox** patterns: cache-first for static assets, network-first for HTML, stale-while-revalidate for APIs. +- Use **Navigation Preload** to parallelize SW startup with navigation requests. + +### Connection-Aware Loading +```js +if (navigator.connection?.saveData || navigator.connection?.effectiveType === '2g') { + // skip non-essential assets +} +``` + +## 8. Rendering Performance + +### Avoid Layout Thrashing +```js +// BAD: read-write-read-write +for (const el of items) { + el.style.width = el.offsetWidth + 10 + 'px'; +} + +// GOOD: batch reads, then writes +const widths = items.map(el => el.offsetWidth); +items.forEach((el, i) => el.style.width = widths[i] + 10 + 'px'); +``` + +### Use `requestAnimationFrame` for visual updates +```js +function animate() { + // visual update + requestAnimationFrame(animate); +} +``` + +### Avoid Forced Synchronous Layout +- Reading layout properties (`offsetWidth`, `getBoundingClientRect()`, `getComputedStyle()`) after writes triggers reflow. + +### Virtualization +- For long lists, render only visible items (windowing). +- Use `IntersectionObserver` for lazy-load triggers: +```js +const io = new IntersectionObserver((entries) => { + entries.forEach(e => { + if (e.isIntersecting) loadContent(e.target); + }); +}, { rootMargin: '200px' }); +``` + +## 9. DOM & Memory + +### Efficient DOM +- Use `DocumentFragment` for batch insertions. +- Prefer `textContent` over `innerHTML` for plain text (also XSS-safer). +- Use event delegation on a parent rather than per-child listeners. + +### Memory Leaks +- Remove event listeners (or use `AbortSignal`). +- Clear intervals/timeouts. +- Avoid retaining DOM nodes in closures after removal. +- Use `WeakMap` / `WeakRef` / `FinalizationRegistry` for caches keyed by objects. + +## 10. Modules & Bundling + +### ES Modules +- Use native ESM (`<script type="module">`) where possible. +- Use **import maps** for bare specifiers without bundling: +```html +<script type="importmap"> +{ "imports": { "react": "https://esm.sh/react@18" } } +</script> +``` + +### Bundling Best Practices +- Tree-shake with ESM imports. +- Code-split per route. +- Avoid barrel files (`index.js` re-exports) that defeat tree-shaking. +- Use modern targets (ES2020+) — drop legacy transpilation when possible. +- Use differential serving via `<script type="module">` / `nomodule`. + +## 11. Third-Party Scripts + +- Load with `async` or `defer`. +- Use `<iframe loading="lazy">` and the `credentialless` attribute when applicable. +- Self-host critical third-party assets when feasible. +- Use **Partytown** to move third-party scripts to a worker. +- Use `fetchpriority="low"` for analytics. +- Audit with WebPageTest's Third-Party report. + +## 12. Build & Delivery + +### Compression +- Brotli for static text assets (pre-compress at build time, level 11). +- Gzip as fallback. +- Zstandard for emerging support. + +### Minification +- Minify HTML, CSS, JS, SVG. +- Remove unused CSS (PurgeCSS / similar). + +### CDN +- Serve static assets from a CDN with edge caching. +- Use HTTP/3 + 0-RTT where available. +- Place origin close to users; use multi-region. + +## 13. Common Clean Code Principles + +### Performance Code Hygiene +- **Measure first, optimize second.** Don't optimize without profiling data. +- **Profile in production-like conditions** (real devices, throttled CPU/network). +- **Test on slow devices** — Moto G Power class for realistic mid-tier mobile. +- Use Chrome DevTools Performance panel, Lighthouse, and WebPageTest. +- Set **performance budgets** (e.g., JS ≤200KB, LCP ≤2.5s) and enforce in CI. +- Prefer **clarity over cleverness**; modern JS engines optimize idiomatic code well. +- Avoid premature micro-optimizations (e.g., `for` vs `forEach` rarely matters). +- **Cache invalidation**: use content hashes in filenames. +- **Avoid global state mutations** in hot paths. +- **Minimize work in event handlers**; defer non-critical work. + +### Accessibility & Performance Together +- Semantic HTML reduces JS needed for behavior (e.g., `<details>`, `<dialog>`). +- Use `prefers-reduced-motion` to skip expensive animations. +```css +@media (prefers-reduced-motion: reduce) { + * { animation: none !important; transition: none !important; } +} +``` + +## 14. Cutting-Edge (Progressive Enhancement) + +- **View Transitions API** (Chromium): smooth cross-document transitions. +```css +@view-transition { navigation: auto; } +``` +- **Speculation Rules** (Chromium): prerender next page. +- **`scheduler.yield()`** (Chromium): cleaner task yielding. +- **Compression Streams API**: `new CompressionStream('gzip')`. +- **`Cache API`** for runtime caching outside Service Workers. +- **`Navigator.scheduling.isInputPending()`** (Chromium): yield to user input. +- **Back/Forward Cache (bfcache)**: avoid `unload` listeners; use `pagehide` instead. Avoid `Cache-Control: no-store` if possible. + +## 15. Diagnostic Workflow + +1. **Field data first**: Use CrUX / RUM to find real-user pain points. +2. **Lab repro**: Use Lighthouse / DevTools to reproduce. +3. **Profile**: Performance panel for runtime, Coverage tab for unused code, Network panel for waterfall. +4. **Hypothesize and fix one thing at a time**. +5. **Validate** with before/after measurements. +6. **Monitor** with continuous RUM via `web-vitals`. + +## 16. Common Anti-Patterns to Avoid + +- Lazy-loading the LCP image. +- Missing `width`/`height` on images and embeds. +- Loading entire icon fonts when 5 SVGs would do. +- Synchronous third-party scripts in `<head>`. +- Animating layout-triggering properties. +- Polling with `setInterval` instead of event-driven updates. +- Shipping a massive client bundle for a static page (use SSG/SSR). +- Re-rendering entire trees on every state change. +- Forgetting to remove event listeners on unmount. +- Using `JSON.parse(JSON.stringify())` for deep clones (use `structuredClone`). +- Bundling polyfills for features all target browsers support. +- Ignoring `prefers-reduced-data` and `Save-Data` headers. diff --git a/.agents/skills/project-discipline-guides/mirrors/performance/codex_mirror.md b/.agents/skills/project-discipline-guides/mirrors/performance/codex_mirror.md new file mode 100644 index 000000000..53fdfcecf --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/performance/codex_mirror.md @@ -0,0 +1,2420 @@ +# Redundancy Mirror: Performance Development Common Knowledge + +This guide reflects standard performance engineering knowledge I would apply by default without needing project-specific guidance. It focuses on modern browser APIs, shipped platform features, progressive enhancement, standard syntax, and clean implementation principles. + +## Performance Mindset + +Performance is a product feature. Optimize for real user outcomes, not just benchmark scores. + +Default priorities: + +1. Load useful content quickly. +2. Keep the UI responsive during interaction. +3. Avoid layout instability. +4. Avoid wasting network, CPU, memory, and battery. +5. Measure before and after changes. +6. Prefer simple architectural choices that prevent performance problems over later micro-optimizations. + +Key user-centered metrics: + +- **LCP**: Largest Contentful Paint. Measures when the main content is likely visible. +- **INP**: Interaction to Next Paint. Measures responsiveness across user interactions. +- **CLS**: Cumulative Layout Shift. Measures visual stability. +- **TTFB**: Time to First Byte. Measures backend/network response start. +- **FCP**: First Contentful Paint. Measures when something useful starts rendering. +- **TBT**: Total Blocking Time. Lab proxy for main-thread blocking before interactivity. +- **Long tasks**: Main-thread tasks over 50 ms that delay user input and rendering. + +General rule: optimize the critical path first. The most important bytes, requests, and work are those needed to render and interact with the initial viewport. + +## Measurement Basics + +Use both lab and field data. + +Lab tools: + +- Lighthouse +- Chrome DevTools Performance panel +- Network panel +- Coverage panel +- Memory panel +- Performance Insights +- WebPageTest +- Bundle analyzers +- Framework profilers, such as React DevTools Profiler + +Field data: + +- Real User Monitoring +- `PerformanceObserver` +- Navigation Timing +- Resource Timing +- Long Animation Frames / Long Tasks where available +- Core Web Vitals collection +- Server logs and CDN analytics + +Do not assume local development performance reflects production. Development builds often include extra checks, unminified code, source maps, slower rendering, and disabled optimizations. + +Measure on realistic devices and networks: + +- Mid-tier Android devices +- Low-power laptops +- Slow 4G / fast 3G simulations +- Cold cache and warm cache +- Authenticated and unauthenticated flows +- Realistic data volume + +Useful browser APIs: + +```js +performance.mark("start-work"); +// work +performance.mark("end-work"); +performance.measure("work", "start-work", "end-work"); + +const entries = performance.getEntriesByType("measure"); +``` + +```js +new PerformanceObserver((list) => { + for (const entry of list.getEntries()) { + console.log(entry.name, entry.duration); + } +}).observe({ entryTypes: ["measure", "navigation", "resource"] }); +``` + +Use `console.time()` and `console.timeEnd()` for quick local checks, but prefer `performance.mark()` and `performance.measure()` for structured instrumentation. + +## Core Web Vitals + +### LCP + +Improve LCP by: + +- Rendering the LCP element early. +- Avoiding client-side rendering delays for primary content. +- Reducing TTFB. +- Avoiding render-blocking CSS and scripts. +- Preloading the LCP image when appropriate. +- Avoiding lazy loading for above-the-fold hero images. +- Using properly sized, compressed images. +- Serving responsive images. +- Avoiding slow font swaps that delay text rendering. +- Avoiding unnecessary animation or opacity delays on primary content. + +Good image pattern: + +```html +<img + src="/hero-1280.avif" + srcset="/hero-640.avif 640w, /hero-1280.avif 1280w, /hero-1920.avif 1920w" + sizes="100vw" + width="1280" + height="720" + fetchpriority="high" + decoding="async" + alt="" +> +``` + +Use `fetchpriority="high"` sparingly, usually for the real LCP image. Overusing high priority can harm other critical resources. + +Avoid: + +```html +<img src="/hero.jpg" loading="lazy"> +``` + +for above-the-fold LCP images. + +### INP + +Improve INP by: + +- Keeping event handlers short. +- Avoiding long synchronous work. +- Splitting large tasks. +- Debouncing or throttling high-frequency events. +- Deferring non-urgent updates. +- Reducing DOM size and expensive style/layout work. +- Avoiding unnecessary framework re-renders. +- Using event delegation where appropriate. +- Moving CPU-heavy work to Web Workers. +- Avoiding forced synchronous layout in interaction handlers. + +Bad: + +```js +button.addEventListener("click", () => { + expensiveCalculation(); + renderHugeList(); + sendAnalyticsSynchronously(); +}); +``` + +Better: + +```js +button.addEventListener("click", () => { + updateImmediateUI(); + + setTimeout(() => { + expensiveCalculation(); + renderDeferredContent(); + }, 0); + + navigator.sendBeacon("/analytics", JSON.stringify({ action: "click" })); +}); +``` + +Modern task yielding: + +```js +if ("scheduler" in window && "yield" in scheduler) { + await scheduler.yield(); +} else { + await new Promise((resolve) => setTimeout(resolve, 0)); +} +``` + +Use progressive enhancement for `scheduler.yield()` and related Scheduling APIs because support varies. + +### CLS + +Prevent layout shift by: + +- Setting `width` and `height` on images and videos. +- Using CSS `aspect-ratio`. +- Reserving space for ads, embeds, banners, and async content. +- Avoiding inserting content above existing content unless caused by user action. +- Avoiding late font metric changes. +- Using `font-display` intentionally. +- Avoiding animations that affect layout properties. + +Good: + +```css +.media { + aspect-ratio: 16 / 9; +} + +img, +video { + max-width: 100%; + height: auto; +} +``` + +Prefer transform/opacity animations: + +```css +.card { + transition: transform 160ms ease, opacity 160ms ease; +} + +.card:hover { + transform: translateY(-2px); +} +``` + +Avoid layout-triggering animation: + +```css +.card:hover { + top: -2px; + width: 105%; +} +``` + +## HTML Performance + +Use semantic HTML. It improves parsing, accessibility, browser behavior, and maintainability. + +Prefer native elements: + +- `<button>` for actions +- `<a>` for navigation +- `<img>` for images +- `<video>` for video +- `<dialog>` for modal dialogs where appropriate +- `<details>` and `<summary>` for disclosure UI +- Form controls for input rather than custom widgets + +Native controls usually have better accessibility, keyboard behavior, mobile support, and less JavaScript. + +Specify dimensions: + +```html +<img src="/image.avif" width="800" height="600" alt="Description"> +``` + +Use responsive images: + +```html +<picture> + <source srcset="/image.avif" type="image/avif"> + <source srcset="/image.webp" type="image/webp"> + <img src="/image.jpg" width="800" height="600" alt="Description"> +</picture> +``` + +Use `srcset` and `sizes` for resolution and viewport-aware loading: + +```html +<img + src="/product-800.jpg" + srcset="/product-400.jpg 400w, /product-800.jpg 800w, /product-1200.jpg 1200w" + sizes="(max-width: 700px) 100vw, 50vw" + width="800" + height="600" + loading="lazy" + decoding="async" + alt="Product" +> +``` + +Use lazy loading for below-the-fold images and iframes: + +```html +<img src="/below-fold.jpg" loading="lazy" decoding="async" alt=""> +<iframe src="/embed" loading="lazy"></iframe> +``` + +Do not lazy-load critical above-the-fold content. + +Use resource hints carefully: + +```html +<link rel="preconnect" href="https://cdn.example.com"> +<link rel="dns-prefetch" href="https://cdn.example.com"> +<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin> +<link rel="modulepreload" href="/assets/app.js"> +``` + +Guidance: + +- Use `preconnect` for critical third-party origins used early. +- Use `dns-prefetch` for less critical origins. +- Use `preload` for critical resources the browser would otherwise discover late. +- Use `modulepreload` for important JavaScript modules. +- Avoid preloading too much. It can compete with more important resources. + +Use async/defer correctly: + +```html +<script src="/non-critical.js" defer></script> +<script type="module" src="/app.js"></script> +``` + +Notes: + +- Classic scripts without `async` or `defer` block parsing. +- `defer` scripts execute after parsing and preserve order. +- `async` scripts execute as soon as loaded and do not preserve order. +- `type="module"` scripts are deferred by default. +- Avoid blocking scripts in `<head>` unless truly required. + +Use progressive HTML streaming when server rendering. Send the shell and critical content as soon as possible. + +Avoid excessive DOM depth and node count. Very large DOMs increase style, layout, memory, and interaction cost. + +## CSS Performance + +CSS blocks rendering, so keep critical CSS small. + +Best practices: + +- Inline only truly critical CSS when beneficial. +- Load non-critical CSS later. +- Remove unused CSS. +- Avoid shipping entire design systems when only a few rules are used. +- Prefer simple selectors. +- Avoid deeply nested selectors. +- Avoid expensive universal selector patterns over large subtrees. +- Avoid unnecessary `!important`. +- Use cascade layers where helpful for predictable overrides. + +Modern CSS features I would use by default when supported by target browsers: + +- Custom properties +- Logical properties +- `clamp()`, `min()`, `max()` +- `aspect-ratio` +- Container queries +- Cascade layers +- `:is()` +- `:where()` +- `:has()` with care +- Native CSS nesting, if supported by project browser targets +- Media query range syntax +- `@supports` +- `content-visibility` +- `contain` +- `prefers-reduced-motion` +- `prefers-color-scheme` +- `color-scheme` +- Subgrid where available and appropriate + +Use feature queries: + +```css +@supports (container-type: inline-size) { + .card-list { + container-type: inline-size; + } + + @container (min-width: 40rem) { + .card { + grid-template-columns: 12rem 1fr; + } + } +} +``` + +Use containment for isolated components: + +```css +.widget { + contain: layout paint style; +} +``` + +Use `content-visibility` for large offscreen sections: + +```css +.section { + content-visibility: auto; + contain-intrinsic-size: 800px; +} +``` + +This can reduce initial rendering cost, but test carefully because it affects rendering, find-in-page behavior in some cases, accessibility timing, and scroll behavior. + +Use `@starting-style` for entry transitions where supported: + +```css +.panel { + opacity: 1; + transition: opacity 150ms ease; +} + +@starting-style { + .panel { + opacity: 0; + } +} +``` + +Use `@supports` for progressive enhancement around newer features: + +```css +@supports selector(:has(*)) { + .field:has(input:invalid) { + border-color: red; + } +} +``` + +Avoid animating layout properties: + +Expensive: + +- `width` +- `height` +- `top` +- `left` +- `right` +- `bottom` +- `margin` +- `padding` +- `border-width` +- `font-size` +- `grid-template-*` + +Usually cheaper: + +- `transform` +- `opacity` + +Use `will-change` sparingly: + +```css +.menu { + will-change: transform; +} +``` + +Remove or avoid persistent `will-change` on many elements because it can increase memory usage. + +Respect reduced motion: + +```css +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: 0.001ms !important; + } +} +``` + +Use `font-display`: + +```css +@font-face { + font-family: "Inter"; + src: url("/fonts/inter.woff2") format("woff2"); + font-display: swap; +} +``` + +Use modern font formats: + +- Prefer WOFF2. +- Use variable fonts when they reduce total font bytes. +- Subset fonts. +- Avoid loading unused weights and styles. +- Preload only critical fonts. +- Use system font stacks when brand constraints allow. + +System font stack: + +```css +body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} +``` + +## JavaScript Loading + +JavaScript is expensive because it must be downloaded, parsed, compiled, and executed. + +Default principles: + +- Ship less JavaScript. +- Split by route and feature. +- Load non-critical JavaScript after critical rendering. +- Prefer server-rendered or static HTML for initial content where possible. +- Avoid hydration work that does not create immediate user value. +- Avoid large dependencies for small utilities. +- Use modern ESM builds. +- Use tree-shaking-friendly imports. +- Avoid top-level side effects in modules. +- Avoid legacy transpilation unless target browsers require it. + +Use dynamic imports: + +```js +button.addEventListener("click", async () => { + const { openEditor } = await import("./editor.js"); + openEditor(); +}); +``` + +Prefer named imports from tree-shakable packages: + +```js +import { format } from "date-fns"; +``` + +Avoid importing entire libraries when only a small function is needed: + +```js +import _ from "lodash"; +``` + +Use import maps only when appropriate and supported by deployment constraints: + +```html +<script type="importmap"> +{ + "imports": { + "utils": "/js/utils.js" + } +} +</script> +``` + +Use modern syntax when supported by build targets: + +- `let` and `const` +- Arrow functions +- Classes where useful +- Template literals +- Destructuring +- Default parameters +- Rest/spread +- Optional chaining +- Nullish coalescing +- Logical assignment +- Private class fields +- Static class fields +- Top-level `await` in modules where appropriate +- `Array.prototype.at` +- `Object.hasOwn` +- `structuredClone` +- `Promise.allSettled` +- `Promise.any` +- `AbortController` +- `WeakMap` +- `WeakSet` +- `Map` +- `Set` + +Use nullish coalescing instead of `||` when valid falsy values matter: + +```js +const limit = options.limit ?? 20; +``` + +Use optional chaining for safe property access: + +```js +const city = user.profile?.address?.city; +``` + +Use `Object.hasOwn()`: + +```js +if (Object.hasOwn(config, key)) { + // own property +} +``` + +Use `structuredClone()` for deep cloning supported data: + +```js +const copy = structuredClone(data); +``` + +Avoid JSON clone for general cloning because it loses types and fails on unsupported values. + +## Main Thread Work + +Keep the main thread available for input, rendering, and scrolling. + +Avoid long synchronous operations: + +- Large JSON parsing +- Large loops +- Complex sorting/filtering +- Syntax highlighting huge documents +- Client-side image processing +- Expensive markdown rendering +- Large DOM updates +- Heavy framework reconciliation + +Chunk work: + +```js +async function processItems(items) { + for (let i = 0; i < items.length; i += 100) { + const chunk = items.slice(i, i + 100); + processChunk(chunk); + + await new Promise((resolve) => setTimeout(resolve, 0)); + } +} +``` + +Prefer `requestIdleCallback` for low-priority work where supported, with fallback: + +```js +const scheduleIdle = window.requestIdleCallback + ? window.requestIdleCallback + : (callback) => setTimeout(() => callback({ timeRemaining: () => 0 }), 1); + +scheduleIdle(() => { + warmCache(); +}); +``` + +Do not use idle callbacks for work required for the next interaction. + +Use `requestAnimationFrame` for visual updates: + +```js +requestAnimationFrame(() => { + element.style.transform = `translateX(${x}px)`; +}); +``` + +Separate reads and writes to avoid layout thrashing: + +Bad: + +```js +for (const item of items) { + const height = item.offsetHeight; + item.style.height = `${height + 10}px`; +} +``` + +Better: + +```js +const heights = items.map((item) => item.offsetHeight); + +items.forEach((item, index) => { + item.style.height = `${heights[index] + 10}px`; +}); +``` + +Avoid forced synchronous layout reads after writes: + +Layout-triggering reads include: + +- `offsetWidth` +- `offsetHeight` +- `offsetTop` +- `offsetLeft` +- `clientWidth` +- `clientHeight` +- `scrollWidth` +- `scrollHeight` +- `getBoundingClientRect()` +- `getComputedStyle()` + +They are not bad by themselves, but they become expensive when interleaved with DOM writes. + +## Web Workers + +Use Web Workers for CPU-heavy work that does not need direct DOM access. + +Good candidates: + +- Parsing large files +- Data transformation +- Search indexing +- Compression +- Image manipulation with OffscreenCanvas where supported +- Syntax highlighting +- Crypto-heavy non-WebCrypto tasks +- Expensive calculations + +Example: + +```js +const worker = new Worker(new URL("./worker.js", import.meta.url), { + type: "module", +}); + +worker.postMessage({ type: "process", payload: data }); + +worker.addEventListener("message", (event) => { + renderResult(event.data); +}); +``` + +Transfer large binary data instead of copying: + +```js +worker.postMessage(buffer, [buffer]); +``` + +Use Comlink-like abstractions only if they reduce complexity and do not hide important performance costs. + +Use `SharedArrayBuffer` only when the app has proper cross-origin isolation headers and a real need. + +## Network Performance + +Reduce round trips, bytes, and blocking dependencies. + +Best practices: + +- Use HTTP/2 or HTTP/3 where available. +- Compress text assets with Brotli or gzip. +- Use CDN caching for static assets. +- Use immutable hashed filenames. +- Set strong cache headers. +- Avoid unnecessary third-party scripts. +- Reduce redirects. +- Avoid document-blocking third-party resources. +- Keep cookies small, especially on frequently requested origins. +- Serve assets from geographically close locations. +- Avoid cache-busting query strings unless intentional. +- Use `stale-while-revalidate` where appropriate. +- Avoid serial request waterfalls. + +Cache header pattern for hashed static assets: + +```http +Cache-Control: public, max-age=31536000, immutable +``` + +For HTML: + +```http +Cache-Control: no-cache +``` + +or a short max-age with revalidation, depending on app needs. + +Use `fetch()` with aborts: + +```js +const controller = new AbortController(); + +const response = await fetch("/api/search", { + signal: controller.signal, +}); + +controller.abort(); +``` + +Timeout wrapper: + +```js +async function fetchWithTimeout(url, options = {}, timeout = 8000) { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeout); + + try { + return await fetch(url, { + ...options, + signal: controller.signal, + }); + } finally { + clearTimeout(id); + } +} +``` + +Use request deduplication for identical in-flight requests. + +Avoid fetching data that is already embedded in the initial HTML when SSR can provide it safely. + +Prefer pagination, cursor loading, or virtualization over fetching huge datasets. + +Use `navigator.sendBeacon()` for analytics during page unload: + +```js +navigator.sendBeacon("/analytics", JSON.stringify(eventData)); +``` + +Use `keepalive` for small fetches when needed: + +```js +fetch("/analytics", { + method: "POST", + body: JSON.stringify(eventData), + keepalive: true, +}); +``` + +## Fetching And Data + +Use parallel requests when independent: + +```js +const [user, settings] = await Promise.all([ + fetchUser(), + fetchSettings(), +]); +``` + +Avoid accidental waterfalls: + +Bad: + +```js +const user = await fetchUser(); +const settings = await fetchSettings(); +``` + +when `settings` does not depend on `user`. + +Use `Promise.allSettled()` when partial failure is acceptable: + +```js +const results = await Promise.allSettled([fetchA(), fetchB()]); +``` + +Abort stale requests in search/autocomplete: + +```js +let currentController; + +async function search(query) { + currentController?.abort(); + + currentController = new AbortController(); + + const response = await fetch(`/search?q=${encodeURIComponent(query)}`, { + signal: currentController.signal, + }); + + return response.json(); +} +``` + +Debounce user input: + +```js +function debounce(fn, delay) { + let id; + + return (...args) => { + clearTimeout(id); + id = setTimeout(() => fn(...args), delay); + }; +} +``` + +Throttle scroll/resize work: + +```js +function throttle(fn, interval) { + let last = 0; + + return (...args) => { + const now = performance.now(); + + if (now - last >= interval) { + last = now; + fn(...args); + } + }; +} +``` + +Prefer `ResizeObserver` and `IntersectionObserver` to polling. + +```js +const observer = new IntersectionObserver((entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + loadCard(entry.target); + observer.unobserve(entry.target); + } + } +}); + +observer.observe(element); +``` + +```js +const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + updateLayout(entry.contentRect); + } +}); + +resizeObserver.observe(container); +``` + +## Images + +Images are often the largest assets. + +Best practices: + +- Use AVIF where suitable. +- Use WebP as a broad fallback. +- Use JPEG for photos when AVIF/WebP unavailable. +- Use PNG only when lossless transparency or exact pixels are needed. +- Use SVG for icons and simple vector graphics. +- Compress images. +- Resize images to display dimensions. +- Avoid serving 4000 px images into 400 px containers. +- Use `srcset` and `sizes`. +- Use `loading="lazy"` below the fold. +- Use `decoding="async"` for non-critical images. +- Use `fetchpriority="high"` for the true LCP image. +- Reserve space with `width`, `height`, or `aspect-ratio`. +- Avoid layout-shifting placeholders. +- Avoid base64 inlining large images into CSS or HTML. +- Use CSS gradients only when they are lighter and appropriate. +- Avoid animated GIFs for large animations; prefer video. + +Video instead of GIF: + +```html +<video autoplay muted loop playsinline width="640" height="360"> + <source src="/animation.webm" type="video/webm"> + <source src="/animation.mp4" type="video/mp4"> +</video> +``` + +Use `poster` for videos: + +```html +<video controls preload="metadata" poster="/poster.jpg"> + <source src="/video.webm" type="video/webm"> + <source src="/video.mp4" type="video/mp4"> +</video> +``` + +Avoid `preload="auto"` for many videos. + +## Fonts + +Fonts can block text rendering or cause layout shifts. + +Best practices: + +- Use system fonts when acceptable. +- Use WOFF2. +- Subset font files. +- Limit weights, styles, and character sets. +- Use variable fonts if they reduce total bytes. +- Use `font-display`. +- Preload critical fonts. +- Avoid loading icon fonts; prefer inline SVG or SVG sprites. +- Match fallback font metrics where possible. +- Avoid render-blocking external font CSS when self-hosting is feasible. + +Example: + +```html +<link + rel="preload" + href="/fonts/inter-var.woff2" + as="font" + type="font/woff2" + crossorigin +> +``` + +```css +@font-face { + font-family: "Inter"; + src: url("/fonts/inter-var.woff2") format("woff2"); + font-weight: 100 900; + font-display: swap; +} +``` + +Use `size-adjust`, `ascent-override`, `descent-override`, and `line-gap-override` when carefully matching fallback metrics: + +```css +@font-face { + font-family: "Inter Fallback"; + src: local("Arial"); + size-adjust: 107%; + ascent-override: 90%; + descent-override: 22%; + line-gap-override: 0%; +} +``` + +## Rendering And Layout + +Avoid unnecessary reflows and repaints. + +Best practices: + +- Keep DOM size reasonable. +- Avoid deeply nested layout structures. +- Prefer CSS layout over JS layout. +- Use Flexbox and Grid appropriately. +- Use CSS containment for isolated regions. +- Avoid reading layout repeatedly during writes. +- Avoid expensive box shadows and filters on many elements. +- Avoid backdrop filters over large areas unless tested. +- Avoid fixed-position large layers with expensive effects. +- Avoid unnecessary compositing layers. +- Use virtualization for long lists. + +Virtualize long lists rather than rendering thousands of nodes. + +Basic idea: + +```js +const visibleItems = items.slice(startIndex, endIndex); +``` + +Use established virtualization libraries for complex production cases. + +CSS Grid and Flexbox are generally efficient, but pathological layouts with many items, nested grids, or frequent layout changes can still be expensive. + +Use `transform: translate(...)` for movement instead of positioning properties. + +## Animation + +Animations should be smooth, interruptible, and respectful of user preferences. + +Default rules: + +- Animate `transform` and `opacity`. +- Avoid layout-triggering animation. +- Use CSS animations/transitions for simple state changes. +- Use Web Animations API for complex imperative animation. +- Use `requestAnimationFrame` for JS-driven visual updates. +- Avoid running animations when offscreen. +- Pause background animations when hidden. +- Respect `prefers-reduced-motion`. +- Avoid animating huge blurred elements or filters. + +Web Animations API: + +```js +element.animate( + [ + { transform: "translateY(8px)", opacity: 0 }, + { transform: "translateY(0)", opacity: 1 }, + ], + { + duration: 180, + easing: "ease-out", + fill: "both", + } +); +``` + +Cancel animations when no longer needed: + +```js +const animation = element.animate(keyframes, options); +animation.cancel(); +``` + +Use Page Visibility API: + +```js +document.addEventListener("visibilitychange", () => { + if (document.hidden) { + pauseWork(); + } else { + resumeWork(); + } +}); +``` + +## Event Handling + +Use efficient event handling. + +Best practices: + +- Use event delegation for many similar children. +- Use passive listeners for scroll/touch listeners where `preventDefault()` is not needed. +- Avoid heavy work in input handlers. +- Debounce text input queries. +- Throttle resize and scroll updates. +- Use `pointer` events instead of separately handling mouse and touch when possible. +- Clean up event listeners. +- Use `AbortController` for listener cleanup. + +Passive listener: + +```js +window.addEventListener("scroll", onScroll, { passive: true }); +``` + +Abortable listener: + +```js +const controller = new AbortController(); + +element.addEventListener("click", onClick, { + signal: controller.signal, +}); + +controller.abort(); +``` + +Event delegation: + +```js +list.addEventListener("click", (event) => { + const button = event.target.closest("[data-action]"); + + if (!button) return; + + handleAction(button.dataset.action); +}); +``` + +Avoid attaching thousands of listeners when one delegated listener will do. + +## Memory + +Memory problems often appear as sluggishness, crashes, or degraded responsiveness. + +Avoid: + +- Retaining detached DOM nodes. +- Keeping large arrays forever. +- Unbounded caches. +- Unremoved event listeners. +- Long-lived closures over large objects. +- Storing duplicate normalized data. +- Rendering hidden but heavy UI trees. +- Leaking timers, observers, sockets, and workers. + +Use cleanup patterns: + +```js +const controller = new AbortController(); + +function mount() { + window.addEventListener("resize", onResize, { + signal: controller.signal, + }); +} + +function unmount() { + controller.abort(); +} +``` + +Disconnect observers: + +```js +observer.disconnect(); +resizeObserver.disconnect(); +mutationObserver.disconnect(); +``` + +Terminate workers: + +```js +worker.terminate(); +``` + +Use bounded caches: + +```js +class LruCache { + constructor(limit = 100) { + this.limit = limit; + this.map = new Map(); + } + + get(key) { + if (!this.map.has(key)) return undefined; + + const value = this.map.get(key); + this.map.delete(key); + this.map.set(key, value); + + return value; + } + + set(key, value) { + if (this.map.has(key)) { + this.map.delete(key); + } + + this.map.set(key, value); + + if (this.map.size > this.limit) { + const oldest = this.map.keys().next().value; + this.map.delete(oldest); + } + } +} +``` + +Use `WeakMap` for metadata associated with object lifetimes: + +```js +const metadata = new WeakMap(); +metadata.set(element, { initialized: true }); +``` + +## Storage + +Use storage intentionally. + +Options: + +- `localStorage`: synchronous, small data, can block main thread. +- `sessionStorage`: synchronous, tab-scoped. +- IndexedDB: async, better for larger structured data. +- Cache Storage: request/response caching, service workers. +- Cookies: sent with requests; keep small. +- OPFS: useful for file-like data in supported browsers. + +Avoid large synchronous `localStorage` reads during startup. + +Bad: + +```js +const state = JSON.parse(localStorage.getItem("huge-state")); +``` + +Better: defer, reduce, or use async storage. + +Use IndexedDB for larger client data. Use a small wrapper library if it reduces boilerplate. + +## Service Workers And Offline + +Service workers can improve repeat visits but add complexity. + +Use for: + +- Offline support +- App shell caching +- Runtime caching +- Background sync where supported +- Push notifications where appropriate +- Faster repeat navigations + +Avoid: + +- Stale critical assets due to poor update strategy. +- Caching personalized or sensitive responses incorrectly. +- Blocking first load with service worker setup. +- Overly broad cache rules. +- Cache growth without cleanup. + +Register after initial load unless early control is necessary: + +```js +if ("serviceWorker" in navigator) { + window.addEventListener("load", () => { + navigator.serviceWorker.register("/service-worker.js"); + }); +} +``` + +Use Workbox or equivalent for non-trivial caching strategies. + +Common strategies: + +- Cache first for immutable static assets. +- Network first for frequently changing data. +- Stale while revalidate for assets where brief staleness is acceptable. +- Network only for sensitive or non-cacheable requests. + +## Third-Party Scripts + +Third-party resources are common performance risks. + +Best practices: + +- Audit every third-party script. +- Remove unused vendors. +- Load third-party scripts after critical rendering when possible. +- Use `async` or `defer`. +- Self-host when licensing, updates, and security allow. +- Use facades for heavy embeds. +- Lazy-load maps, videos, chat widgets, and social embeds. +- Limit tag manager sprawl. +- Monitor third-party CPU cost, not just bytes. +- Use `preconnect` only for critical third-party origins. +- Avoid blocking consent, analytics, or personalization scripts on initial render unless required. + +Embed facade example: show a lightweight thumbnail/button, load the real iframe only after click. + +## Bundling + +Production bundles should be optimized. + +Expected defaults: + +- Minification. +- Tree shaking. +- Code splitting. +- Route-level chunks. +- Dynamic imports for rarely used features. +- Modern browser output when possible. +- Differential serving only when needed. +- CSS extraction where beneficial. +- Dead code elimination. +- Dependency deduplication. +- Avoid source maps in public production unless intentional. +- Analyze large dependencies. +- Avoid polyfilling entire APIs when small targeted fallbacks suffice. + +Use `sideEffects` correctly in package metadata when authoring libraries: + +```json +{ + "sideEffects": false +} +``` + +Only mark false if modules truly have no import-time side effects. + +Avoid barrel files that defeat tree shaking in some build setups. + +Avoid importing Node-oriented packages into browser bundles. + +Prefer native APIs over dependencies when practical: + +- `fetch` instead of request libraries +- `Intl` instead of many date/number formatting utilities +- `URL` and `URLSearchParams` +- `structuredClone` +- `crypto.randomUUID` +- `AbortController` +- CSS instead of JS animation libraries for simple transitions + +## Modern JavaScript APIs + +Use `Intl` for locale-aware formatting: + +```js +const formatter = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", +}); + +formatter.format(1234.56); +``` + +```js +const dateFormatter = new Intl.DateTimeFormat("en-US", { + dateStyle: "medium", + timeStyle: "short", +}); +``` + +Use `URL` and `URLSearchParams`: + +```js +const url = new URL("/search", location.origin); +url.searchParams.set("q", query); +``` + +Use `crypto.randomUUID()`: + +```js +const id = crypto.randomUUID(); +``` + +Use Web Crypto for cryptographic operations, not hand-rolled crypto. + +Use `AbortController` for cancelable async work: + +```js +const controller = new AbortController(); +doWork({ signal: controller.signal }); +controller.abort(); +``` + +Use streams for large data where appropriate: + +```js +const response = await fetch("/large-file"); +const reader = response.body.getReader(); +``` + +Use `TextEncoder` and `TextDecoder` for binary/text conversion: + +```js +const bytes = new TextEncoder().encode("hello"); +const text = new TextDecoder().decode(bytes); +``` + +Use `Blob` and object URLs for generated files: + +```js +const blob = new Blob([content], { type: "text/plain" }); +const url = URL.createObjectURL(blob); + +URL.revokeObjectURL(url); +``` + +Always revoke object URLs when no longer needed. + +## Web Components + +Use Web Components when they fit the architecture, especially for framework-agnostic reusable widgets. + +Performance considerations: + +- Shadow DOM encapsulation can help style isolation but adds complexity. +- Avoid excessive custom element creation for huge lists. +- Defer non-critical work in `connectedCallback`. +- Clean up listeners, observers, and timers in `disconnectedCallback`. +- Use attributes and properties intentionally. +- Avoid layout-heavy lifecycle work. + +Example cleanup: + +```js +class MyElement extends HTMLElement { + #controller = new AbortController(); + + connectedCallback() { + this.addEventListener("click", this.#onClick, { + signal: this.#controller.signal, + }); + } + + disconnectedCallback() { + this.#controller.abort(); + } + + #onClick = () => { + // handle + }; +} + +customElements.define("my-element", MyElement); +``` + +## Accessibility And Performance + +Accessibility and performance usually reinforce each other. + +Best practices: + +- Use native HTML. +- Avoid excessive JavaScript for basic interactions. +- Keep focus handling simple. +- Do not trap users in slow custom controls. +- Respect reduced motion. +- Avoid layout shifts that disorient users. +- Make loading states clear. +- Avoid infinite spinners without progress or retry paths. +- Do not delay meaningful content for decorative effects. + +Fast, semantic interfaces are usually more accessible. + +## Framework Performance + +General principles apply across React, Vue, Svelte, Angular, Solid, and others. + +Best practices: + +- Avoid unnecessary re-renders. +- Keep component state local when possible. +- Do not put rapidly changing state at the top of large trees. +- Memoize only when it prevents real work. +- Avoid expensive computations in render functions. +- Split large components by responsibility. +- Use virtualization for large lists. +- Use stable keys. +- Avoid using array index as key when list order can change. +- Lazy-load heavy routes and components. +- Prefer server rendering or static rendering for content-heavy pages. +- Avoid hydration for static islands where possible. +- Use partial hydration/islands/resumability when framework supports it and complexity is justified. +- Profile before adding memoization everywhere. + +React common knowledge: + +- Use `memo`, `useMemo`, and `useCallback` selectively. +- Avoid creating unnecessary objects/functions if they cause expensive child renders. +- Use transitions for non-urgent updates. +- Use `Suspense` appropriately. +- Keep controlled inputs responsive. +- Avoid expensive work during render. +- Use production builds. + +React example: + +```js +const filteredItems = useMemo(() => { + return items.filter((item) => item.name.includes(query)); +}, [items, query]); +``` + +Do not use `useMemo` for trivial calculations unless it avoids meaningful downstream cost. + +Vue common knowledge: + +- Keep reactive state minimal. +- Avoid making huge immutable datasets deeply reactive when unnecessary. +- Use computed properties for derived data. +- Use async components for code splitting. +- Use `v-memo` or equivalent optimizations selectively. +- Use stable keys. + +Svelte common knowledge: + +- Reactive declarations are efficient but should not hide expensive repeated work. +- Avoid huge keyed updates when virtualization is needed. +- Use stores carefully to avoid broad invalidation. + +Angular common knowledge: + +- Use `OnPush` change detection where appropriate. +- Use `trackBy` in repeated lists. +- Avoid heavy template expressions. +- Lazy-load routes. +- Use signals where appropriate in modern Angular. + +## Server Rendering + +Server rendering can improve initial content visibility but can also add hydration cost. + +Best practices: + +- Stream HTML where possible. +- Send critical content early. +- Avoid blocking rendering on non-critical data. +- Avoid duplicating expensive work on server and client. +- Keep hydration payloads small. +- Use partial hydration or islands when suitable. +- Cache rendered output when possible. +- Avoid huge serialized JSON blobs. +- Escape serialized data safely. +- Avoid client-rendering the whole page after SSR. + +Hydration cost matters. A page that displays quickly but is unresponsive for seconds is still slow. + +## Edge And Backend Interaction + +Frontend performance often depends on backend behavior. + +Best practices: + +- Reduce TTFB. +- Cache at CDN/edge when possible. +- Use compression. +- Avoid unnecessary redirects. +- Optimize database queries. +- Avoid over-fetching. +- Use pagination. +- Return only needed fields. +- Use HTTP caching headers. +- Use ETags or Last-Modified for revalidation. +- Coalesce requests. +- Use server push alternatives carefully; HTTP/2 push is generally not a default recommendation. +- Prefer `103 Early Hints` where infrastructure supports it and it is beneficial. + +API design performance: + +- Avoid chatty request patterns. +- Batch when it reduces latency without over-fetching. +- Provide endpoints shaped for critical UI needs. +- Avoid sending huge nested objects for simple screens. +- Support compression. +- Support conditional requests. + +## Clean Code Principles For Performance + +Write code that makes performance characteristics obvious. + +Principles: + +- Prefer straightforward code. +- Keep data flow clear. +- Avoid hidden global work. +- Avoid import-time side effects. +- Make expensive operations explicit. +- Name functions according to cost when relevant. +- Cache only with clear invalidation rules. +- Keep caches bounded. +- Do not prematurely memoize everything. +- Avoid clever micro-optimizations that obscure behavior. +- Prefer algorithms with appropriate complexity. +- Use data structures intentionally. +- Avoid repeated linear scans in hot paths. +- Avoid nested loops over large collections when maps/sets would help. +- Avoid mutating shared state unpredictably. +- Keep rendering logic separate from data processing when useful. +- Add comments for non-obvious performance decisions. + +Use `Map` for keyed lookup: + +```js +const usersById = new Map(users.map((user) => [user.id, user])); +``` + +Use `Set` for membership: + +```js +const selectedIds = new Set(selected.map((item) => item.id)); + +const visible = items.filter((item) => selectedIds.has(item.id)); +``` + +Avoid repeated search: + +```js +// Potentially O(n*m) +orders.map((order) => ({ + ...order, + user: users.find((user) => user.id === order.userId), +})); +``` + +Better: + +```js +const usersById = new Map(users.map((user) => [user.id, user.id])); + +orders.map((order) => ({ + ...order, + user: usersById.get(order.userId), +})); +``` + +Use early returns: + +```js +function renderItem(item) { + if (!item.visible) return null; + + return createItemView(item); +} +``` + +Avoid work in loops when it can be hoisted: + +```js +const formatter = new Intl.NumberFormat("en-US"); + +for (const value of values) { + labels.push(formatter.format(value)); +} +``` + +Do not instantiate expensive formatters repeatedly in hot paths. + +## Algorithmic Performance + +Know common complexity: + +- Array lookup by index: O(1) +- Array search: O(n) +- Object/Map lookup: usually O(1) +- Set membership: usually O(1) +- Sorting: usually O(n log n) +- Nested loops: often O(n²) + +Common improvements: + +- Pre-index data with `Map`. +- Use `Set` for membership. +- Sort once, not repeatedly. +- Cache derived data when inputs are stable. +- Avoid repeated parsing or formatting. +- Use incremental updates instead of recomputing everything. +- Use binary search for sorted data when appropriate. +- Use tries or indexes for heavy search/autocomplete when justified. +- Use workers for expensive operations. + +Do not replace clear O(n) code with obscure tricks unless profiling shows it matters. + +## DOM Updates + +Batch DOM changes. + +Use `DocumentFragment` for many insertions: + +```js +const fragment = document.createDocumentFragment(); + +for (const item of items) { + const element = renderItem(item); + fragment.append(element); +} + +list.append(fragment); +``` + +Use `replaceChildren()`: + +```js +list.replaceChildren(...nodes); +``` + +Use `<template>` for repeated structures: + +```html +<template id="row-template"> + <li class="row"></li> +</template> +``` + +```js +const template = document.querySelector("#row-template"); + +const node = template.content.firstElementChild.cloneNode(true); +``` + +Avoid repeated `innerHTML += ...` because it reparses and recreates content. + +Be careful with `innerHTML` for security and performance. Prefer DOM APIs for untrusted content. + +Use `textContent` instead of `innerHTML` for plain text: + +```js +element.textContent = user.name; +``` + +## Observers + +Use observer APIs instead of polling. + +Intersection Observer: + +- Lazy-load content. +- Start/stop animations. +- Track visibility. +- Trigger prefetch when near viewport. + +Resize Observer: + +- Respond to element size changes. +- Avoid global resize hacks. + +Mutation Observer: + +- React to DOM changes when integration requires it. +- Avoid observing huge subtrees unnecessarily. +- Disconnect when done. + +Performance Observer: + +- Collect performance entries. +- Track long tasks, layout shifts, resources, navigation, paint entries where supported. + +## Progressive Enhancement + +Use modern features when beneficial, with fallbacks for unsupported browsers. + +Patterns: + +```js +if ("IntersectionObserver" in window) { + // enhanced behavior +} else { + // fallback +} +``` + +```css +@supports (content-visibility: auto) { + .section { + content-visibility: auto; + } +} +``` + +Progressive enhancement candidates: + +- `content-visibility` +- Container queries +- `:has()` +- View Transitions API +- Scheduler API +- Speculation Rules +- Navigation API +- Popover API +- Anchor positioning +- OffscreenCanvas +- WebGPU +- Compression Streams +- File System Access API +- Shared Storage / privacy sandbox features, depending on use case + +Cutting-edge features should not be required for core functionality unless the product explicitly targets browsers that support them. + +## Speculation And Prefetching + +Use predictive loading carefully. + +Options: + +```html +<link rel="prefetch" href="/next-page"> +<link rel="prerender" href="/next-page"> +``` + +Speculation Rules API, where supported: + +```html +<script type="speculationrules"> +{ + "prerender": [ + { + "source": "list", + "urls": ["/next"] + } + ] +} +</script> +``` + +Guidance: + +- Prefetch likely next navigations. +- Avoid prefetching large resources on constrained networks. +- Respect data saver signals where available. +- Do not prerender pages with unsafe side effects. +- Avoid wasting user bandwidth. +- Be careful with authenticated or personalized pages. + +Check connection hints: + +```js +const connection = navigator.connection; + +if (!connection?.saveData && connection?.effectiveType !== "2g") { + prefetchLikelyRoute(); +} +``` + +Network Information API support varies, so treat it as enhancement. + +## View Transitions + +Use View Transitions API as progressive enhancement. + +```js +if (document.startViewTransition) { + document.startViewTransition(() => updateDOM()); +} else { + updateDOM(); +} +``` + +Keep transitions short and avoid masking slow navigations. A transition should not delay meaningful content. + +## Popover And Dialog + +Use native `popover` and `<dialog>` where supported and appropriate. + +Popover: + +```html +<button popovertarget="menu">Menu</button> +<div id="menu" popover> + ... +</div> +``` + +Dialog: + +```html +<dialog id="modal"> + <form method="dialog"> + <button>Close</button> + </form> +</dialog> +``` + +Native primitives can reduce JavaScript and improve accessibility, but test behavior across target browsers. + +## Forms + +Use native form behavior. + +Best practices: + +- Use correct input types. +- Use built-in validation where suitable. +- Avoid custom controls unless needed. +- Avoid JS-heavy validation on every keystroke. +- Debounce async validation. +- Keep input handlers fast. +- Use `autocomplete`. +- Use `inputmode`. +- Use `enterkeyhint`. +- Use `required`, `min`, `max`, `pattern`, and other native constraints. + +Example: + +```html +<input + type="email" + autocomplete="email" + required +> +``` + +```html +<input + type="text" + inputmode="numeric" + autocomplete="one-time-code" +> +``` + +## Security And Performance + +Security choices can affect performance, and performance choices must not weaken security. + +Common knowledge: + +- Do not use unsafe `innerHTML` with untrusted data. +- Avoid loading unnecessary third-party scripts. +- Use Content Security Policy where possible. +- Use Subresource Integrity for third-party static assets. +- Use HTTPS. +- Use secure cookies. +- Use COOP/COEP when required for advanced APIs like `SharedArrayBuffer`. +- Avoid exposing secrets in client bundles. +- Avoid client-side crypto schemes as a substitute for server security. + +SRI example: + +```html +<script + src="https://cdn.example.com/library.js" + integrity="sha384-..." + crossorigin="anonymous" + defer +></script> +``` + +## Mobile Performance + +Mobile devices are constrained by CPU, memory, thermal throttling, and network quality. + +Best practices: + +- Test on real mobile hardware. +- Reduce JavaScript. +- Avoid large layout shifts. +- Keep touch handlers fast. +- Use passive listeners. +- Avoid large fixed backgrounds and heavy filters. +- Avoid excessive box shadows. +- Avoid scroll-linked JavaScript effects. +- Use responsive images. +- Avoid autoplaying heavy media. +- Keep tap targets stable. +- Avoid input jank from controlled components. +- Minimize memory use. +- Account for virtual keyboard resizing. + +Use modern viewport units carefully: + +- `svh` +- `lvh` +- `dvh` + +Example: + +```css +.panel { + min-height: 100svh; +} +``` + +Use `dvh` when dynamic viewport behavior is desired, and test mobile browser UI behavior. + +## Scroll Performance + +Avoid scroll jank. + +Best practices: + +- Avoid heavy scroll handlers. +- Use passive listeners. +- Prefer CSS `position: sticky` over JS scroll positioning. +- Prefer Intersection Observer over scroll polling. +- Avoid animating expensive properties during scroll. +- Avoid large repaint areas. +- Avoid scroll-linked effects unless necessary. +- Use CSS scroll snap where appropriate. +- Respect reduced motion. + +CSS scroll snap: + +```css +.carousel { + display: flex; + overflow-x: auto; + scroll-snap-type: x mandatory; +} + +.slide { + scroll-snap-align: start; +} +``` + +## Tables And Large Data Views + +For large data-heavy UIs: + +- Use pagination or virtualization. +- Avoid rendering thousands of rows at once. +- Use sticky headers carefully. +- Avoid complex cell renderers in every row. +- Memoize column definitions where relevant. +- Avoid inline closures in hot cell paths if they cause rerenders. +- Precompute expensive derived values. +- Keep DOM depth shallow. +- Use `table` for real tabular data unless virtualization constraints require another structure. +- Preserve accessibility when virtualizing. + +## Canvas, SVG, And WebGL + +Use the right rendering technology. + +DOM: + +- Good for semantic UI and moderate element counts. + +SVG: + +- Good for scalable vector graphics, icons, diagrams, moderate data visualization. +- Can become slow with many nodes. + +Canvas: + +- Good for many drawn objects and pixel operations. +- Requires custom accessibility and hit testing. + +WebGL/WebGPU: + +- Good for advanced graphics and GPU computation. +- Higher complexity. + +Canvas performance: + +- Batch draw calls. +- Avoid unnecessary clears. +- Scale for device pixel ratio intentionally. +- Use OffscreenCanvas in workers where supported and useful. +- Avoid reading pixels frequently. +- Use requestAnimationFrame. +- Pause when hidden. + +SVG performance: + +- Reduce node count. +- Avoid heavy filters. +- Avoid animating many SVG attributes. +- Use symbols/reuse where appropriate. +- Prefer CSS transforms where possible. + +## JSON And Data Serialization + +Large JSON can block parsing. + +Best practices: + +- Do not send unnecessary fields. +- Paginate large responses. +- Compress responses. +- Stream where possible. +- Avoid embedding huge JSON in HTML. +- Parse off-main-thread for very large payloads. +- Consider binary formats only when justified. +- Avoid repeated serialization in hot paths. + +Use `Response.json()` for normal payloads: + +```js +const data = await response.json(); +``` + +For very large data, consider streaming NDJSON or chunked formats. + +## Internationalization + +Use built-in `Intl` APIs. + +Examples: + +```js +new Intl.NumberFormat(locale).format(number); +new Intl.DateTimeFormat(locale, options).format(date); +new Intl.RelativeTimeFormat(locale).format(-1, "day"); +new Intl.ListFormat(locale).format(items); +new Intl.DisplayNames(locale, { type: "region" }).of("US"); +new Intl.PluralRules(locale).select(count); +``` + +Performance note: create formatters once and reuse them in hot paths. + +## Testing Performance + +Performance testing should be repeatable. + +Common practices: + +- Define budgets. +- Test production builds. +- Test realistic routes. +- Test cold and warm cache. +- Test with CPU/network throttling. +- Track bundle size. +- Track Core Web Vitals. +- Track regressions in CI where possible. +- Use smoke tests for severe regressions. +- Avoid overfitting to a single Lighthouse score. + +Example budgets: + +- Initial JS under a defined compressed size. +- LCP under 2.5s for target percentile. +- CLS under 0.1. +- INP under 200ms. +- No individual long task over a defined threshold for critical flows. +- Image sizes capped by use case. +- Route chunks capped. + +## Performance Budgets + +Budgets make performance enforceable. + +Budget categories: + +- JavaScript bytes +- CSS bytes +- Image bytes +- Total transfer size +- Number of requests +- LCP +- INP +- CLS +- TTFB +- Long tasks +- Hydration time +- Route transition time +- Memory usage +- Third-party script cost + +Budgets should be tied to user and business goals, not arbitrary perfection. + +## Build And Deployment + +Production configuration matters. + +Best practices: + +- Use production mode. +- Minify JS and CSS. +- Compress assets. +- Hash static filenames. +- Use long-term caching for immutable assets. +- Generate modern bundles. +- Avoid unnecessary polyfills. +- Remove dev-only code. +- Remove console/debug code if policy requires it. +- Ensure source maps are intentional. +- Analyze bundles. +- Avoid shipping test utilities. +- Use CDN caching. +- Verify headers. + +Common headers: + +```http +Content-Encoding: br +Cache-Control: public, max-age=31536000, immutable +``` + +Security/performance headers where appropriate: + +```http +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Embedder-Policy: require-corp +``` + +Only use COOP/COEP when you understand cross-origin embedding implications. + +## Dependency Hygiene + +Dependencies have performance, security, and maintenance costs. + +Best practices: + +- Prefer native APIs for small tasks. +- Audit dependency size. +- Avoid duplicate packages. +- Avoid importing all locales or plugins accidentally. +- Use ESM builds. +- Keep dependencies updated. +- Avoid abandoned packages. +- Avoid packages with large transitive dependency trees for trivial functionality. +- Check whether package code is browser-friendly. +- Avoid server-only modules in client bundles. + +Common replacements: + +- Native `fetch` instead of HTTP clients for simple requests. +- `URLSearchParams` instead of query string libraries. +- `Intl` instead of heavy formatting libraries. +- CSS transitions instead of animation libraries for simple effects. +- Native date formatting where enough. +- `crypto.randomUUID()` instead of UUID libraries when acceptable. + +## Common Anti-Patterns + +Avoid: + +- Rendering everything on the client when static HTML would work. +- Loading the app before showing any content. +- Huge JavaScript bundles for simple pages. +- Unbounded client-side caches. +- Large synchronous `localStorage` reads on startup. +- Heavy scroll handlers. +- Layout reads mixed with writes. +- Lazy-loading the LCP image. +- Missing image dimensions. +- Shipping uncompressed images. +- Loading many font weights. +- Icon fonts for small icon sets. +- Blocking third-party scripts. +- Hydrating static content unnecessarily. +- Rendering thousands of list items. +- Recomputing derived data on every render. +- Overusing global state. +- Overusing memoization without profiling. +- Using array indexes as unstable keys. +- Importing entire utility libraries. +- Large CSS frameworks with most rules unused. +- Complex selectors over huge DOM trees. +- Animating layout properties. +- Keeping observers/listeners alive after unmount. +- Using polling instead of observers. +- Fetching data serially when independent. +- Retrying failed requests aggressively without backoff. +- Sending analytics synchronously. +- Ignoring mobile hardware. +- Optimizing only Lighthouse while real users remain slow. + +## Clean Async Patterns + +Handle async work predictably. + +Use `try`/`catch`: + +```js +try { + const response = await fetch("/api/items"); + + if (!response.ok) { + throw new Error(`Request failed: ${response.status}`); + } + + return await response.json(); +} catch (error) { + showError(error); +} +``` + +Use aborts: + +```js +async function loadData(signal) { + const response = await fetch("/api/data", { signal }); + return response.json(); +} +``` + +Avoid fire-and-forget promises unless intentionally handled: + +```js +void sendAnalytics(event); +``` + +Use backoff for retries: + +```js +async function retry(fn, attempts = 3) { + for (let attempt = 1; attempt <= attempts; attempt += 1) { + try { + return await fn(); + } catch (error) { + if (attempt === attempts) throw error; + + await new Promise((resolve) => { + setTimeout(resolve, 2 ** attempt * 100); + }); + } + } +} +``` + +## Error Handling And Performance + +Failures can cause performance issues. + +Best practices: + +- Avoid infinite retry loops. +- Use exponential backoff. +- Abort obsolete requests. +- Cache fallback data where appropriate. +- Avoid blocking the whole UI on partial failures. +- Fail fast for invalid input. +- Keep error UI lightweight. +- Log enough to debug without excessive payloads. +- Avoid throwing inside hot render paths when validation can happen earlier. + +## Responsive Design Performance + +Responsive design should not load unnecessary assets. + +Best practices: + +- Use responsive images. +- Avoid hiding huge desktop assets on mobile after downloading them. +- Use CSS media queries and `picture` sources. +- Avoid separate DOM trees for mobile and desktop when one responsive structure works. +- Use container queries for component-level adaptation. +- Avoid JS-driven resize layout when CSS can solve it. + +Example: + +```html +<picture> + <source media="(max-width: 600px)" srcset="/image-small.avif"> + <source media="(min-width: 601px)" srcset="/image-large.avif"> + <img src="/image-large.jpg" width="1200" height="800" alt=""> +</picture> +``` + +## Privacy And Data Saver + +Respect user constraints. + +Best practices: + +- Avoid unnecessary prefetch on constrained networks. +- Avoid autoplaying heavy media. +- Respect `Save-Data` where available. +- Provide lower-data alternatives. +- Avoid excessive analytics. +- Avoid collecting performance data with sensitive payloads. + +```js +if (navigator.connection?.saveData) { + disableNonEssentialPrefetch(); +} +``` + +## Practical Optimization Order + +A common default optimization sequence: + +1. Measure the problem. +2. Identify whether the bottleneck is network, server, rendering, JavaScript, memory, or third-party code. +3. Fix the largest critical-path issue first. +4. Remove unnecessary work. +5. Defer non-critical work. +6. Split expensive work. +7. Cache carefully. +8. Optimize assets. +9. Re-measure. +10. Add budget or regression guard if the issue is likely to return. + +## Default Recommendations I Would Apply Automatically + +For most modern web projects, I would default to: + +- Semantic HTML. +- Server-rendered or static initial content where suitable. +- Production builds with minification and compression. +- Modern ESM JavaScript. +- Route-level code splitting. +- Dynamic imports for heavy features. +- Minimal third-party scripts. +- Responsive AVIF/WebP images with dimensions. +- No lazy loading for LCP images. +- Lazy loading for below-the-fold images/iframes. +- WOFF2 fonts with `font-display`. +- System fonts unless custom fonts are needed. +- CSS Grid/Flexbox instead of JS layout. +- Container queries where useful. +- `content-visibility` for large offscreen sections when tested. +- Passive scroll/touch listeners. +- Debounced input-driven network requests. +- `AbortController` for stale async work. +- Web Workers for heavy CPU tasks. +- `Map` and `Set` for hot lookup paths. +- `Intl` APIs for formatting. +- Bounded caches. +- Cleanup for listeners, observers, workers, timers, and object URLs. +- Performance budgets in CI for serious applications. +- Real-device testing for user-facing performance work. + +This is the baseline “common knowledge” I would expect to bring to performance development without needing it repeated in a project-specific guide. diff --git a/.agents/skills/project-discipline-guides/mirrors/performance/gemini_mirror.md b/.agents/skills/project-discipline-guides/mirrors/performance/gemini_mirror.md new file mode 100644 index 000000000..f28dfb831 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/performance/gemini_mirror.md @@ -0,0 +1,105 @@ +This "Redundancy Mirror" outlines the foundational and modern performance best practices, syntax, and APIs inherent to my training. It covers critical areas from loading and execution to rendering and memory management. + +--- + +# Performance Development: The Redundancy Mirror + +## 1. Resource Loading & Prioritization + +### Resource Hints +* **`preconnect`**: Establishes early connections (DNS + TCP + TLS) to critical third-party origins. +* **`dns-prefetch`**: Resolves DNS for origins that will be needed soon (lower overhead than preconnect). +* **`preload`**: Forces high-priority fetch of resources needed for the current page (e.g., fonts, hero images, critical scripts). Use `as` and `type` attributes. +* **`prefetch`**: Low-priority fetch for resources likely needed for the *next* navigation. +* **Speculation Rules API**: Modern, JSON-based configuration for prefetching and prerendering entire pages based on user intent. + +### JavaScript Loading Patterns +* **`defer`**: Downloads script in parallel, executes after HTML parsing but before `DOMContentLoaded`. Maintains order. +* **`async`**: Downloads in parallel, executes as soon as it's ready (interrupts parsing). No order guarantee. +* **ES Modules (`type="module"`)**: Deferred by default. Enables tree-shaking and avoids global scope pollution. +* **Dynamic `import()`**: Enables code-splitting and on-demand loading of modules. + +### Fetch Priority API +* **`fetchpriority`**: Attribute (`high`, `low`, `auto`) for `<img>`, `<link>`, `<script>`, and `fetch()` calls to signal relative importance to the browser's preload scanner. + +--- + +## 2. Rendering Performance & CSS + +### The Pixel Pipeline +* **Avoid Layout Thrashing**: Prevent "Read-Write-Read" cycles. Batch DOM reads (e.g., `getBoundingClientRect`) and writes (e.g., `style.height`) to avoid forced synchronous layouts. +* **Compositor-Only Properties**: Prioritize `transform` and `opacity` for animations. These bypass the Layout and Paint stages, running on the GPU. + +### Modern CSS Performance +* **`content-visibility: auto`**: Skips rendering of off-screen elements until they approach the viewport. +* **`contain` property**: Informs the browser that an element’s subtree is independent, allowing for layout/paint optimizations (`contain: layout paint`). +* **`will-change`**: Hints to the browser which properties will change (e.g., `will-change: transform`), allowing for early layer creation. Use sparingly. +* **`aspect-ratio`**: Sets a preferred aspect ratio to prevent Layout Shift (CLS) before media loads. +* **CSS Grid & Flexbox**: Generally more performant for complex layouts than legacy float/table hacks. + +--- + +## 3. Execution Performance (JavaScript) + +### Task Scheduling +* **`requestAnimationFrame (rAF)`**: Synchronizes code execution with the display's refresh rate (typically 60fps). Ideal for visual updates. +* **`requestIdleCallback (rIC)`**: Schedules low-priority background tasks when the browser is idle. +* **`scheduler.yield()` (Prioritized Task Scheduling API)**: Breaks up long tasks to keep the main thread responsive, allowing the browser to interleave input handling or rendering. + +### Off-Main-Thread Execution +* **Web Workers**: Moves heavy computational logic (data processing, complex math) to a background thread. +* **Shared Workers**: Allows multiple scripts (in different tabs/iframes) to communicate with a single background worker. +* **Service Workers**: Manages caching and network requests, enabling offline capabilities and faster repeat visits. + +### Memory Management +* **Weak References**: Use `WeakMap`, `WeakSet`, and `WeakRef` to hold references to objects without preventing garbage collection. +* **Event Listener Cleanup**: Always remove event listeners (`removeEventListener`) or use the `once: true` / `signal` (AbortController) options to prevent leaks. +* **Object Pooling**: Reuse objects in high-frequency scenarios (like game loops) to reduce Garbage Collection (GC) pressure. + +--- + +## 4. Modern Browser APIs + +### Observation APIs +* **Intersection Observer API**: Efficiently detects when an element enters/exits the viewport (perfect for lazy-loading and infinite scroll). +* **Resize Observer API**: Tracks changes to an element's dimensions without the overhead of window `resize` events. +* **Mutation Observer API**: Watch for specific changes to the DOM tree. + +### Measurement & Telemetry +* **Performance API**: + * `performance.now()`: High-resolution timestamps. + * `performance.mark()` and `performance.measure()`: Custom timing for User Timing API. + * `PerformanceObserver`: Efficiently streams performance entries (LCP, FID, CLS, long tasks). +* **Beacon API (`navigator.sendBeacon`)**: Sends small amounts of data to a server asynchronously on page unload without blocking navigation. + +### Data & Streams +* **Streams API**: Processes data (e.g., large fetch responses) chunk-by-chunk without loading the entire payload into memory. +* **Compression Streams API**: Compresses and decompresses data streams natively in the browser (Gzip/Deflate). + +--- + +## 5. Network & Optimization + +### Image & Media +* **Native Lazy Loading**: `loading="lazy"` on `<img>` and `<iframe>`. +* **Modern Formats**: Prefer WebP and AVIF over JPEG/PNG. +* **`srcset` and `sizes`**: Serve appropriately sized images based on device resolution and layout width. +* **`decoding="async"`**: Allows image decoding to happen off the main thread. + +### Transport +* **HTTP/2 & HTTP/3**: Leverages multiplexing, header compression (HPACK/QPACK), and reduced latency. +* **Compression**: Ensure Brotli (preferable) or Gzip is enabled for all text-based assets. + +--- + +## 6. Clean Code & Architectural Patterns + +### Efficient Logic +* **Debouncing & Throttling**: Limit the execution rate of high-frequency event handlers (scroll, resize, typing). +* **Early Returns**: Reduce cognitive load and unnecessary nesting. +* **Virtualization**: For large lists, only render the DOM nodes currently visible in the scroll container. + +### Modern Syntax for Performance +* **Spread/Rest vs. Object.assign**: Understanding that modern engines optimize spread operations well, but excessive use on large objects can create overhead. +* **Typed Arrays**: Use `Int32Array`, `Float64Array`, etc., for high-performance numeric data handling. +* **`for` vs `forEach`**: Traditional `for` loops or `for...of` are often faster in tight loops than array prototype methods that involve callback overhead. diff --git a/.agents/skills/project-discipline-guides/mirrors/performance/mirror.md b/.agents/skills/project-discipline-guides/mirrors/performance/mirror.md new file mode 100644 index 000000000..7f3f2de02 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/performance/mirror.md @@ -0,0 +1,89 @@ +This unified "Lowest Common Denominator (LCD) Mirror" represents the intersection of web performance practices explicitly documented in the Gemini, Claude, and Codex mirrors. + +# Web Performance: Lowest Common Denominator (LCD) Mirror + +## 1. Resource Loading & Prioritization + +### Resource Hints +* **`preconnect`**: Establishes early connections (DNS, TCP, TLS) to critical third-party origins. +* **`dns-prefetch`**: Resolves DNS early for origins that will be required soon. +* **`preload`**: Forces high-priority fetch for critical resources needed for the current page. +* **`prefetch`**: Low-priority fetch for resources likely needed for the next navigation. +* **Speculation Rules API**: JSON-based configuration for prefetching or prerendering pages based on user intent. + +### Script Execution Patterns +* **`defer`**: Downloads scripts in parallel and executes them in order after HTML parsing. +* **`async`**: Downloads scripts in parallel and executes them as soon as they are ready, potentially interrupting parsing. +* **ES Modules (`type="module"`)**: Scripts are deferred by default and enable better tree-shaking. +* **Dynamic `import()`**: Enables code-splitting and on-demand loading of JavaScript modules. + +### Priority Hints +* **`fetchpriority`**: Attribute (`high`, `low`, `auto`) to signal the relative importance of resources (images, scripts, fetches) to the browser. + +--- + +## 2. Rendering & CSS Performance + +### The Pixel Pipeline & Layout +* **Avoid Layout Thrashing**: Batch DOM reads (e.g., `offsetWidth`) and writes (e.g., `style.width`) to prevent forced synchronous layouts. +* **Compositor-Only Properties**: Use `transform` and `opacity` for animations to bypass Layout and Paint stages and utilize the GPU. + +### Modern CSS Features +* **`content-visibility: auto`**: Skips rendering work for off-screen elements until they approach the viewport. +* **`contain`**: Informs the browser that an element’s subtree is independent, isolating layout and paint. +* **`will-change`**: Hints to the browser which properties will change to allow for early optimization (to be used sparingly). +* **`aspect-ratio`**: Sets a preferred aspect ratio to reserve space and prevent Layout Shift (CLS) before media loads. +* **CSS Grid & Flexbox**: Preferred modern layout systems for performance and flexibility. + +--- + +## 3. Execution & Task Scheduling + +### Main Thread Management +* **`requestAnimationFrame` (rAF)**: Synchronizes code execution with the display refresh rate for smooth visual updates. +* **`requestIdleCallback` (rIC)**: Schedules low-priority background tasks during browser idle periods. +* **`scheduler.yield()`**: Breaks up long tasks to keep the main thread responsive to user input and rendering. + +### Off-Main-Thread Execution +* **Web Workers**: Moves heavy computational or data-processing logic to background threads to avoid blocking the UI. + +--- + +## 4. Measurement & Observation + +### Metrics & Core Web Vitals +* **LCP (Largest Contentful Paint)**: Measures loading performance of the largest visible element. +* **CLS (Cumulative Layout Shift)**: Measures visual stability by tracking unexpected layout shifts. + +### Browser Observation APIs +* **`PerformanceObserver`**: Efficiently monitors and streams performance entries (LCP, CLS, resources). +* **`Intersection Observer API`**: Detects when elements enter or exit the viewport (used for lazy-loading). +* **User Timing API**: Uses `performance.mark()` and `performance.measure()` for custom application timing. +* **Beacon API (`navigator.sendBeacon`)**: Sends small amounts of data to a server asynchronously on page unload. + +--- + +## 5. Media & Network Optimization + +### Image Optimization +* **Native Lazy Loading**: `loading="lazy"` attribute for `<img>` and `<iframe>`. +* **Modern Formats**: Use of WebP and AVIF for superior compression. +* **Responsive Images**: Use of `srcset` and `sizes` to serve appropriately sized images. +* **`decoding="async"`**: Allows image decoding to occur off the main thread. + +### Transport & Data +* **HTTP/2 & HTTP/3**: Leverages multiplexing and reduced latency for resource delivery. +* **Compression**: Use of Brotli or Gzip for text-based assets. +* **Streams API**: Processes data (like fetch responses) chunk-by-chunk to reduce memory overhead. + +--- + +## 6. Logic & Memory Management + +### Efficient Implementation +* **Debouncing & Throttling**: Limits the execution rate of high-frequency event handlers (scroll, resize, input). +* **Virtualization**: Renders only the visible subset of items in large lists to reduce DOM nodes. + +### Resource Cleanup +* **Event Listener Management**: Use of `removeEventListener` or `AbortController` (`signal`) to prevent memory leaks. +* **Weak Collections**: Use of `WeakMap` and `WeakSet` to hold object references without preventing garbage collection. diff --git a/.agents/skills/project-discipline-guides/mirrors/security/claude_mirror.md b/.agents/skills/project-discipline-guides/mirrors/security/claude_mirror.md new file mode 100644 index 000000000..07c35fdf3 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/security/claude_mirror.md @@ -0,0 +1,666 @@ +# Security Best Practices: Common Knowledge Guide + +## 1. Input Validation & Sanitization + +### Core Principles +- **Never trust user input** — validate at every trust boundary (client, server, database) +- **Allowlist over denylist** — define what's permitted rather than blocking known-bad patterns +- **Validate type, length, format, and range** for every input +- **Canonicalize before validation** — decode URL encoding, normalize Unicode (NFC/NFKC), resolve paths +- **Reject, don't sanitize** when possible — sanitization is error-prone +- **Validate on the server** — client-side validation is a UX feature, not security + +### Common Validation Patterns +```javascript +// Use built-in validators where available +URL.canParse(input) +Number.isInteger(value) && value >= 0 && value <= MAX +Number.isSafeInteger(value) + +// Email validation: use type="email" + server validation +// Don't write your own regex — use a library + +// Length limits on every string field +if (input.length > MAX_LENGTH) throw new Error('Too long') +``` + +### Path Traversal Prevention +```javascript +import path from 'node:path' + +const safe = path.resolve(BASE_DIR, userInput) +if (!safe.startsWith(BASE_DIR + path.sep)) { + throw new Error('Path traversal detected') +} +``` + +## 2. Output Encoding / XSS Prevention + +### Context-Aware Encoding +- **HTML body context** — encode `&`, `<`, `>`, `"`, `'`, `/` +- **HTML attribute context** — quote attributes, encode `&`, `<`, `>`, `"`, `'` +- **JavaScript context** — use `JSON.stringify()`, never interpolate into JS +- **URL context** — use `encodeURIComponent()` for path/query components +- **CSS context** — only allow allowlisted property values + +### Safe DOM APIs +```javascript +// SAFE — text nodes +element.textContent = userInput +element.innerText = userInput + +// DANGEROUS — parses HTML +element.innerHTML = userInput // XSS risk +element.outerHTML = userInput // XSS risk +document.write(userInput) // XSS risk +eval(userInput) // never use +new Function(userInput) // never use +setTimeout(userInput, 100) // string form is eval + +// SAFE attribute setting +element.setAttribute('data-x', value) // safe for data attrs +element.href = url // validate scheme first + +// Use the Sanitizer API or DOMPurify for HTML +element.setHTML(html) // built-in sanitizer (newer browsers) +``` + +### Trusted Types (Chrome/Edge, polyfillable) +```javascript +// CSP: require-trusted-types-for 'script'; +const policy = trustedTypes.createPolicy('default', { + createHTML: (s) => DOMPurify.sanitize(s) +}) +element.innerHTML = policy.createHTML(userInput) +``` + +## 3. Content Security Policy (CSP) + +```http +Content-Security-Policy: + default-src 'self'; + script-src 'self' 'nonce-{random}' 'strict-dynamic'; + style-src 'self' 'nonce-{random}'; + img-src 'self' data: https:; + connect-src 'self'; + frame-ancestors 'none'; + base-uri 'self'; + form-action 'self'; + object-src 'none'; + upgrade-insecure-requests; + require-trusted-types-for 'script'; +``` + +- **Use nonces or hashes** instead of `'unsafe-inline'` +- **Avoid `'unsafe-eval'`** — refactor code that requires it +- **Use `'strict-dynamic'`** for modern apps with bundlers +- **Set `frame-ancestors 'none'`** to prevent clickjacking (replaces `X-Frame-Options`) +- **Report violations** with `report-to` / `report-uri` directives +- **Test in Report-Only mode** before enforcing + +## 4. Authentication + +### Password Storage +- **Never store plaintext** or reversibly encrypted passwords +- **Use Argon2id** (preferred), `scrypt`, or `bcrypt` — never MD5, SHA-1, SHA-256 alone +- **Per-password random salt** (≥16 bytes), automatically handled by these algorithms +- **Tune work factors** to take ~250-500ms on your hardware +- **Pepper** (server-side secret) optional for defense-in-depth + +```javascript +import argon2 from 'argon2' +const hash = await argon2.hash(password, { type: argon2.argon2id }) +const valid = await argon2.verify(hash, password) +``` + +### Password Policy +- **Minimum length 8-12 chars**, no maximum below 64 +- **Allow all printable Unicode**, including spaces and emoji +- **Check against breach lists** (HIBP API with k-anonymity) +- **No forced periodic rotation** unless compromise suspected (NIST SP 800-63B) +- **No composition rules** (e.g., "must contain symbol") — they push users toward predictable patterns + +### Session Management +- **Use server-issued session IDs**, not client-generated +- **128+ bits of entropy** from a CSPRNG +- **Regenerate session ID on login** (prevents fixation) +- **Invalidate on logout** server-side, not just clear the cookie +- **Idle timeout** (15-30 min for sensitive apps) and absolute timeout +- **Bind sessions to additional context** (IP family, user agent hash) carefully + +### Multi-Factor Authentication +- **Prefer WebAuthn/passkeys** over TOTP over SMS +- **TOTP**: 30-second window, 6 digits, allow ±1 step skew +- **Backup codes**: hashed, single-use, generated client-blind +- **Avoid SMS** where possible (SIM-swap vulnerable) + +### WebAuthn / Passkeys +```javascript +const credential = await navigator.credentials.create({ + publicKey: { + challenge: serverChallenge, + rp: { name: 'Example', id: 'example.com' }, + user: { id: userId, name: email, displayName: name }, + pubKeyCredParams: [{ alg: -7, type: 'public-key' }], + authenticatorSelection: { + residentKey: 'preferred', + userVerification: 'preferred' + }, + timeout: 60_000, + attestation: 'none' + } +}) +``` + +## 5. Authorization + +- **Default deny** — every endpoint requires explicit allow +- **Centralize authorization checks**, don't sprinkle through views +- **Check on every request**, not just on initial navigation +- **Verify object ownership** (IDOR prevention) — `WHERE owner_id = $current_user` +- **Re-check on sensitive operations**, even if recently authenticated +- **Use opaque IDs** (UUIDs) over enumerable integers where appropriate +- **Principle of least privilege** for service accounts and DB users +- **Separate authentication from authorization** logic + +## 6. Cookies + +```http +Set-Cookie: session=abc123; + Secure; # HTTPS only + HttpOnly; # No JS access + SameSite=Lax; # CSRF protection (Strict for sensitive) + Path=/; + Max-Age=3600; + Domain=example.com; # omit for host-only (more secure) +``` + +- **`__Host-` prefix** — requires `Secure`, `Path=/`, no `Domain` +- **`__Secure-` prefix** — requires `Secure` +- **`SameSite=Strict`** for auth cookies; `Lax` is the modern default +- **`SameSite=None`** requires `Secure` +- **Avoid storing tokens in `localStorage`** — accessible to any XSS +- **Partitioned cookies (CHIPS)** for third-party contexts + +## 7. CSRF Protection + +- **SameSite cookies** are first line of defense +- **Synchronizer token pattern** — server-generated, per-session token in form/header +- **Double-submit cookie pattern** — token in cookie + matching header +- **Origin/Referer header validation** for state-changing requests +- **Require POST for state changes** — never use GET to mutate state +- **Custom request headers** (e.g., `X-Requested-With`) for AJAX — triggers CORS preflight + +## 8. CORS + +```javascript +// Server response +Access-Control-Allow-Origin: https://trusted.example.com // not '*' with credentials +Access-Control-Allow-Credentials: true +Access-Control-Allow-Methods: GET, POST +Access-Control-Allow-Headers: Content-Type, Authorization +Access-Control-Max-Age: 86400 +Vary: Origin +``` + +- **Never use `*` with credentials** +- **Allowlist origins explicitly** — don't reflect the `Origin` header without validation +- **Set `Vary: Origin`** when reflecting origins +- **Preflight (OPTIONS)** required for non-simple requests +- **Don't rely on CORS for security** — it's a browser policy, not server enforcement + +## 9. SQL Injection Prevention + +```javascript +// SAFE — parameterized queries +db.query('SELECT * FROM users WHERE id = ?', [userId]) +db.query('SELECT * FROM users WHERE email = $1', [email]) + +// DANGEROUS — string concatenation +db.query(`SELECT * FROM users WHERE id = ${userId}`) +db.query(`SELECT * FROM users WHERE email = '${email}'`) +``` + +- **Always use prepared statements / parameterized queries** +- **ORMs help** but raw queries within them still need parameters +- **Validate and allowlist** when interpolation is unavoidable (table/column names) +- **Use stored procedures** with parameters where appropriate +- **Least-privilege DB accounts** — separate read/write users +- **Escape LIKE wildcards** (`%`, `_`) in user input when used in LIKE patterns + +## 10. Command Injection Prevention + +```javascript +import { execFile } from 'node:child_process' + +// SAFE — argument array, no shell +execFile('git', ['log', '--oneline', branch], callback) + +// DANGEROUS +exec(`git log ${branch}`) // shell interpretation +exec(`ls ${userPath}`) // injection via ;, &&, |, $() +``` + +- **Never invoke a shell** with user input +- **Use `execFile`/`spawn` with argument arrays**, not `exec` or `system` +- **Allowlist commands and arguments** +- **Validate file paths** against expected patterns + +## 11. Cryptography + +### General Rules +- **Don't roll your own crypto** — use vetted libraries +- **Use high-level APIs** (libsodium, `WebCrypto`) over primitives +- **Key derivation** with HKDF or PBKDF2/Argon2 for passwords +- **Constant-time comparison** for secrets (`crypto.timingSafeEqual`) +- **Authenticated encryption** (AES-GCM, ChaCha20-Poly1305) — never raw AES-CBC +- **Random IV/nonce per encryption**, never reuse +- **CSPRNG only** — `crypto.randomBytes`, `crypto.getRandomValues`, never `Math.random` + +### WebCrypto Examples +```javascript +// Random bytes +const bytes = crypto.getRandomValues(new Uint8Array(32)) + +// UUID v4 +const id = crypto.randomUUID() + +// SHA-256 hash +const digest = await crypto.subtle.digest('SHA-256', data) + +// AES-GCM encrypt +const key = await crypto.subtle.generateKey( + { name: 'AES-GCM', length: 256 }, + true, + ['encrypt', 'decrypt'] +) +const iv = crypto.getRandomValues(new Uint8Array(12)) +const ciphertext = await crypto.subtle.encrypt( + { name: 'AES-GCM', iv }, + key, + plaintext +) +``` + +### Algorithm Choices (current) +- **Symmetric**: AES-256-GCM, ChaCha20-Poly1305 +- **Asymmetric**: Ed25519 (signatures), X25519 (key exchange), RSA-OAEP-SHA256 (≥2048-bit, prefer 3072+) +- **Hashing**: SHA-256, SHA-384, SHA-512, BLAKE2/3 +- **Password hashing**: Argon2id, scrypt, bcrypt +- **MAC**: HMAC-SHA256+ +- **Avoid**: MD5, SHA-1, DES, 3DES, RC4, ECB mode, RSA-PKCS1v1.5 + +## 12. TLS / HTTPS + +- **HTTPS everywhere** — including internal services +- **HSTS** with long max-age and `includeSubDomains; preload` +- **TLS 1.2 minimum**, prefer TLS 1.3 +- **Disable old protocols** (SSLv3, TLS 1.0/1.1) +- **Strong cipher suites only** — forward secrecy required +- **Validate certificates** in clients, including hostname +- **Certificate transparency** monitoring +- **OCSP stapling** for performance +- **Use Let's Encrypt or managed certs** with auto-renewal + +```http +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +``` + +## 13. Security Headers + +```http +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Content-Security-Policy: ... +X-Content-Type-Options: nosniff +Referrer-Policy: strict-origin-when-cross-origin +Permissions-Policy: geolocation=(), microphone=(), camera=() +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Embedder-Policy: require-corp +Cross-Origin-Resource-Policy: same-origin +Cache-Control: no-store # for sensitive responses +``` + +- **`X-Frame-Options`** is superseded by CSP `frame-ancestors` +- **`X-XSS-Protection`** is deprecated — rely on CSP +- **COOP/COEP/CORP** enable cross-origin isolation (required for SharedArrayBuffer) + +## 14. JWT / Token Handling + +- **Short-lived access tokens** (5-15 min); separate refresh tokens +- **Validate signature, issuer, audience, expiration, not-before** +- **Use asymmetric (RS256/EdDSA)** for distributed verification +- **Reject `alg: none`** explicitly +- **Don't store sensitive data** in JWT payload — it's only base64 +- **Store securely** — `HttpOnly` cookies for browser apps; avoid `localStorage` +- **Implement revocation** via short TTLs + denylist or stateful sessions +- **Rotate signing keys** with `kid` header +- **Prefer opaque session tokens** for first-party browser apps + +## 15. Secrets Management + +- **Never commit secrets** to source control +- **Use environment variables** or a secrets manager (Vault, AWS Secrets Manager) +- **`.env` files** in `.gitignore`, with `.env.example` template +- **Pre-commit hooks** to scan for secrets (gitleaks, trufflehog) +- **Rotate compromised secrets immediately** and audit usage +- **Different secrets per environment** (dev/staging/prod) +- **Limit secret access** with IAM/RBAC +- **Audit secret access** logs + +## 16. Logging & Monitoring + +### What to Log +- Authentication events (success and failure) +- Authorization failures +- Input validation failures +- Session lifecycle (create, destroy, timeout) +- Privilege changes +- Data access for sensitive resources +- System errors and exceptions + +### What NOT to Log +- Passwords, API keys, tokens, session IDs +- Full credit card numbers, SSNs, PII (mask appropriately) +- Encryption keys +- Personal health information + +### Log Hygiene +- **Structured logging** (JSON) for parsing +- **Sanitize log inputs** — prevent log injection / forging +- **Centralized log aggregation** with retention policies +- **Alerting on anomalies** (auth failure spikes, unusual access patterns) +- **Tamper-evident logs** for security-relevant events + +## 17. Error Handling + +- **Generic error messages to users** — "Invalid credentials," not "User not found" +- **Detailed errors to logs**, with correlation IDs +- **Don't leak stack traces** in production responses +- **Don't expose internal paths, versions, framework names** +- **Fail closed/secure** — default to denying access on error +- **Consistent timing** for sensitive comparisons (login, token check) + +## 18. Rate Limiting & Abuse Prevention + +- **Rate limit by IP, account, and endpoint** +- **Stricter limits on auth endpoints** (login, signup, password reset) +- **Account lockout with care** — DoS risk; prefer exponential backoff or CAPTCHA +- **CAPTCHA** for high-value or repeated suspicious actions +- **Bot detection** for scraping, credential stuffing +- **Distributed rate limiting** for horizontally scaled services + +## 19. Dependency Management + +- **Pin dependency versions** with lockfiles (`package-lock.json`, `pnpm-lock.yaml`) +- **Automated vulnerability scanning** (`npm audit`, Dependabot, Snyk) +- **Update regularly** — patch security vulnerabilities promptly +- **Minimize dependencies** — each one is attack surface +- **Verify package integrity** with subresource integrity (SRI) for CDNs +- **Audit transitive dependencies**, not just direct ones +- **Avoid abandoned packages** — check last update, maintainers + +```html +<script src="https://cdn.example.com/lib.js" + integrity="sha384-..." + crossorigin="anonymous"></script> +``` + +## 20. File Upload Security + +- **Validate file type** by magic bytes, not extension or `Content-Type` +- **Limit file size** at server and proxy levels +- **Scan uploads** for malware +- **Store outside web root** or in object storage +- **Generate new filenames** — never trust user-provided names +- **Set restrictive `Content-Type`** and `Content-Disposition: attachment` for downloads +- **Serve from a separate, cookieless domain** to limit cookie theft via XSS +- **Strip metadata** (EXIF) from images if privacy-sensitive +- **Avoid serving uploads as HTML/SVG** which can contain scripts + +## 21. SSRF (Server-Side Request Forgery) + +- **Allowlist outbound destinations** when fetching user-supplied URLs +- **Block private IP ranges** (RFC1918, link-local, loopback, IMDS endpoints like 169.254.169.254) +- **Resolve DNS once and pin** — defeat DNS rebinding +- **Disable redirects** or validate each hop +- **Use a forward proxy** with policy enforcement +- **Run with limited network access** (egress firewall rules) + +## 22. Deserialization + +- **Don't deserialize untrusted data** with formats that allow code execution (Python `pickle`, Java `ObjectInputStream`, PHP `unserialize`, Node `vm`) +- **Use safe formats** (JSON, MessagePack, Protobuf) with schema validation +- **Validate after parsing** — JSON.parse won't validate structure +- **Use schema validators** (Zod, Ajv, Joi) + +## 23. XML / XXE + +- **Disable external entities** (`DOCTYPE`, `ENTITY`) in XML parsers +- **Disable DTD processing** entirely if possible +- **Prefer JSON** when format is your choice +- **Validate against schema** when XML is required + +## 24. Open Redirects + +```javascript +// SAFE — validate against allowlist +const ALLOWED = ['/dashboard', '/profile', '/settings'] +if (!ALLOWED.includes(target)) target = '/dashboard' + +// SAFE — same-origin only +const url = new URL(target, location.origin) +if (url.origin !== location.origin) target = '/' +``` + +- **Validate redirect targets** against an allowlist +- **Use relative paths** when possible +- **Reject protocol-relative URLs** (`//evil.com`) + +## 25. Clickjacking + +- **`Content-Security-Policy: frame-ancestors 'none'`** (or specific origins) +- **Frame-busting JavaScript** as backup (limited reliability) +- **Sensitive UI** (payments, settings) should not be embeddable + +## 26. Same-Origin Policy & Window Communication + +```javascript +// Open links safely +<a href="..." target="_blank" rel="noopener noreferrer"> + +// postMessage — always check origin +window.addEventListener('message', (e) => { + if (e.origin !== 'https://trusted.example.com') return + // ... handle message +}) + +// Send to specific origin, not '*' +target.postMessage(data, 'https://trusted.example.com') +``` + +- **`rel="noopener"`** prevents `window.opener` access (default since 2021 for `_blank`) +- **`rel="noreferrer"`** also strips Referer +- **Validate `event.origin`** in postMessage handlers +- **Never use `*`** as targetOrigin with sensitive data + +## 27. iframe Sandboxing + +```html +<iframe sandbox="allow-scripts allow-same-origin" + src="..." + loading="lazy" + referrerpolicy="no-referrer"></iframe> +``` + +- **Empty `sandbox`** is most restrictive +- **Don't combine `allow-scripts` and `allow-same-origin`** for untrusted content (escapes sandbox) +- **Use `allow` attribute** to restrict Permissions Policy features + +## 28. Modern JavaScript Security Practices + +```javascript +'use strict' // implicit in modules + +// Object freezing for constants +const CONFIG = Object.freeze({ apiUrl: '...' }) + +// Avoid prototype pollution +const obj = Object.create(null) // no prototype +const safe = { __proto__: null } // same effect +Object.hasOwn(obj, key) // safer than `in` or `hasOwnProperty` + +// Use `Map` over plain object for user-keyed data +const userMap = new Map() + +// Structured clone for deep copy +const copy = structuredClone(original) + +// AbortController for cancellation +const controller = new AbortController() +fetch(url, { signal: controller.signal }) +``` + +### Prototype Pollution Prevention +- **Avoid recursive merge** of untrusted objects into existing objects +- **Reject `__proto__`, `constructor`, `prototype`** keys when parsing +- **Use `Object.create(null)`** for lookup tables +- **Use `Map`** for dynamic key-value storage + +## 29. Browser Storage Choices + +| Storage | Persistence | XSS Accessible | Use For | +|---------|-------------|----------------|---------| +| `HttpOnly` cookie | Per-cookie expiry | No | Session tokens | +| `localStorage` | Until cleared | Yes | Non-sensitive UI state | +| `sessionStorage` | Tab session | Yes | Per-tab UI state | +| IndexedDB | Until cleared | Yes | Large structured data | +| `Cache` API | Until cleared | Yes | Service worker caches | + +- **Don't store secrets in any client-side storage** accessible to JavaScript +- **Encrypt at rest** if sensitive data must be cached client-side (with caveats — keys are also exposed) + +## 30. Service Workers & PWA + +- **HTTPS required** (except localhost) +- **Scope is path-based** — narrow scope to limit risk +- **Validate cached responses** before returning +- **Cache busting strategy** for security updates +- **Be cautious with `fetch` interception** — full request control + +## 31. PII & Privacy + +- **Data minimization** — collect only what you need +- **Encrypt at rest and in transit** +- **Right to deletion** — design for it (GDPR, CCPA) +- **Consent for tracking** (cookies, analytics) +- **Privacy by default** — opt-in, not opt-out +- **Pseudonymization** where possible +- **Data residency** considerations +- **Privacy policy** must reflect actual practice + +## 32. Subresource Integrity & Supply Chain + +```html +<script src="https://cdn.example.com/lib.js" + integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" + crossorigin="anonymous"></script> +``` + +- **SRI for all third-party scripts/styles** +- **Self-host critical dependencies** when possible +- **Lock files committed** for reproducibility +- **Verify package signatures** where supported +- **Audit npm install scripts** — disable with `--ignore-scripts` where feasible + +## 33. Security Testing + +- **Static analysis (SAST)** in CI (ESLint security rules, Semgrep, CodeQL) +- **Dependency scanning (SCA)** (Dependabot, Snyk, npm audit) +- **Dynamic analysis (DAST)** (OWASP ZAP, Burp Suite) +- **Secret scanning** (gitleaks, trufflehog) +- **Penetration testing** for critical applications +- **Threat modeling** during design (STRIDE) +- **Security code review** for sensitive changes +- **Bug bounty / responsible disclosure** program + +## 34. Incident Response + +- **Documented response plan** with roles and contacts +- **Audit logs** retained per compliance needs +- **Backup and recovery** tested regularly +- **Tabletop exercises** for common scenarios +- **Post-mortem** for every incident, blameless + +## 35. Common Code Quality Principles + +- **Single responsibility** — functions/modules do one thing +- **Pure functions** when possible — easier to reason about and test +- **Immutability** by default (`const`, `Object.freeze`, persistent data structures) +- **Fail fast** — assert preconditions, throw on invalid state +- **Make invalid states unrepresentable** through types +- **Separation of concerns** — keep business logic out of presentation +- **DRY, but only for true duplication** — avoid premature abstraction +- **YAGNI** — don't add what you don't need +- **Composition over inheritance** +- **Dependency injection** for testability +- **Clear naming** over comments +- **Small functions** with single levels of abstraction +- **Avoid global mutable state** + +## 36. Modern Browser APIs Worth Knowing (Security-Adjacent) + +- **`crypto.subtle`** — WebCrypto for hashing, encryption, signing +- **`crypto.randomUUID()`** — RFC 4122 v4 UUIDs +- **`crypto.getRandomValues()`** — CSPRNG random bytes +- **`URL` / `URLPattern`** — safe parsing/matching +- **`Sanitizer` / `Element.setHTML()`** — built-in HTML sanitization +- **`Trusted Types`** — sink protection +- **`Permissions API`** — query/request permissions +- **`Credential Management API`** — passkeys, federated auth +- **`Web Authentication API`** — WebAuthn +- **`Storage Access API`** — third-party cookie access +- **`Reporting API`** — CSP/COEP/deprecation reports +- **`AbortController`** — cancellation for fetch and other async ops +- **`structuredClone()`** — safe deep cloning + +## 37. HTTP Methods & Idempotency + +- **GET** — safe, idempotent, no side effects, cacheable +- **HEAD/OPTIONS** — safe, idempotent +- **PUT/DELETE** — idempotent, can have side effects +- **POST/PATCH** — non-idempotent +- **Never mutate state via GET** — CSRF, prefetching, caching, logging risks +- **Idempotency keys** for retry-safe POSTs + +## 38. Defense in Depth + +- **Layered controls** — assume each layer can fail +- **Network segmentation** — separate trust zones +- **Least privilege** at every layer (users, services, processes) +- **Sandboxing** — containers, VMs, browser sandboxes +- **WAF** as an additional layer, not primary defense +- **Monitoring and alerting** to detect bypasses + +## 39. Secure Defaults + +- **Secure by default** — opt out of security, never opt in +- **Deny by default** for permissions and access +- **Minimum permissions** for service accounts, OAuth scopes +- **Disable unused features**, ports, services +- **Latest stable versions** of runtimes and frameworks +- **HTTPS by default**, HTTP redirects to HTTPS + +## 40. OWASP Top 10 Awareness (Current) + +1. **Broken Access Control** +2. **Cryptographic Failures** +3. **Injection** +4. **Insecure Design** +5. **Security Misconfiguration** +6. **Vulnerable and Outdated Components** +7. **Identification and Authentication Failures** +8. **Software and Data Integrity Failures** +9. **Security Logging and Monitoring Failures** +10. **Server-Side Request Forgery (SSRF)** + +Familiarity with the Top 10 (and CWE Top 25) is baseline knowledge for security review. diff --git a/.agents/skills/project-discipline-guides/mirrors/security/codex_mirror.md b/.agents/skills/project-discipline-guides/mirrors/security/codex_mirror.md new file mode 100644 index 000000000..ef99856f1 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/security/codex_mirror.md @@ -0,0 +1,1942 @@ +# Security Development Redundancy Mirror + +This is the security guidance I would apply by default from general engineering knowledge, without needing project-specific instructions. + +## Core Security Principles + +### Default stance + +- Treat all external input as untrusted. +- Validate data at trust boundaries. +- Encode output for the context where it is used. +- Prefer deny-by-default access control. +- Prefer allowlists over blocklists. +- Minimize privilege for users, services, tokens, files, processes, and browser APIs. +- Assume client-side controls are advisory only. +- Enforce all important security decisions on the server. +- Fail closed when authorization, validation, or policy checks cannot complete. +- Avoid leaking whether a user, account, token, resource, or permission exists unless that disclosure is intentional. +- Make security properties explicit in code instead of relying on convention. +- Use well-maintained platform APIs and mature libraries instead of custom cryptography, parsers, sanitizers, auth schemes, or policy engines. +- Keep attack surface small: fewer dependencies, fewer exposed endpoints, fewer permissions, fewer ambient capabilities. +- Design for defense in depth: authentication, authorization, input validation, output encoding, rate limiting, logging, monitoring, isolation, and recovery all matter. + +### Threat modeling defaults + +For any feature, consider: + +- Who can call this? +- What identity is associated with the request? +- What resource is being accessed? +- Who owns that resource? +- What trust boundary is crossed? +- What happens if the input is malicious? +- What happens if this endpoint is called directly, out of order, or repeatedly? +- What happens if the user is authenticated but not authorized? +- What data could be leaked through errors, timing, logs, caches, URLs, browser history, analytics, or third-party scripts? +- What happens if the dependency, webhook sender, queue message, file upload, or callback is forged? +- What happens if the request is replayed? +- What happens if the operation partially succeeds? + +## Authentication + +### General + +- Use a proven identity provider or framework whenever possible. +- Store passwords only with a slow password hashing algorithm such as Argon2id, bcrypt, or scrypt. +- Never store plaintext passwords. +- Never encrypt passwords reversibly. +- Use unique per-password salts. +- Use parameters appropriate for current hardware and operational latency. +- Support password reset through single-use, time-limited tokens. +- Invalidate password reset tokens after use. +- Avoid revealing whether an email address exists during login, signup, and reset flows. +- Require re-authentication for sensitive changes such as password changes, MFA changes, email changes, account deletion, and payment changes. +- Use MFA for privileged users and sensitive applications. +- Prefer phishing-resistant MFA such as WebAuthn/passkeys where practical. +- Protect login, reset, and MFA endpoints with rate limits and abuse detection. +- Do not log passwords, reset tokens, OTP codes, session IDs, bearer tokens, or authorization headers. + +### Password handling + +- Enforce reasonable minimum length. +- Allow long passwords. +- Do not impose arbitrary low maximum lengths. +- Do not require composition rules that reduce usability without improving security much. +- Check new passwords against known-compromised password lists where feasible. +- Allow paste into password fields. +- Use `autocomplete="current-password"` for login password fields. +- Use `autocomplete="new-password"` for new password fields. +- Use `autocomplete="username"` for username/email fields. +- Use `<input type="password">` for password entry. +- Avoid sending passwords through URLs, query strings, analytics, or logs. + +### Session management + +- Use secure, random, high-entropy session identifiers. +- Generate tokens with a cryptographically secure random source. +- Rotate session IDs after login and privilege changes. +- Invalidate sessions on logout. +- Provide server-side revocation where possible. +- Expire sessions after inactivity and absolute lifetime. +- Store web session cookies with: + - `HttpOnly` + - `Secure` + - `SameSite=Lax` or `SameSite=Strict` where compatible with product needs + - a narrow `Path` + - a narrow `Domain` or no `Domain` when host-only is preferred +- Avoid storing long-lived bearer tokens in `localStorage`. +- Prefer `HttpOnly` cookies for browser sessions. +- Treat JWTs as bearer credentials. +- Keep JWT lifetimes short when they cannot be revoked. +- Validate JWT signature, issuer, audience, expiration, not-before, algorithm, and key ID. +- Do not accept unsigned JWTs. +- Do not let clients choose JWT algorithms. +- Avoid putting sensitive or mutable authorization state in long-lived JWT claims. +- Use refresh-token rotation where refresh tokens are used. +- Detect refresh-token reuse. + +## Authorization + +### Access control + +- Authenticate the user, then authorize the specific action on the specific resource. +- Do not rely on hidden UI, disabled buttons, or client-side route guards for authorization. +- Check authorization on every server endpoint, mutation, file download, API call, websocket action, background job trigger, and admin action. +- Use object-level authorization checks to prevent IDOR/BOLA bugs. +- Use function-level authorization checks to prevent users from invoking privileged operations directly. +- Treat resource IDs from the client as untrusted. +- Verify ownership, membership, tenant, role, scope, and policy for each requested resource. +- Avoid broad “admin” checks when the operation needs narrower permission semantics. +- Prefer centralized authorization helpers or policy objects over scattered inline conditions. +- Make authorization failures indistinguishable where appropriate: + - `404` for resources the caller should not know exist. + - `403` when the caller is known but lacks permission and disclosure is acceptable. + - `401` when authentication is required or invalid. + +### Multi-tenancy + +- Include tenant checks in every query that accesses tenant-scoped data. +- Do not trust tenant IDs from client input without verifying membership. +- Avoid global IDs that allow enumeration unless access checks are robust. +- Prefer database constraints, row-level security, or scoped repositories where practical. +- Ensure background jobs, exports, analytics, and admin tools preserve tenant isolation. +- Test cross-tenant access explicitly. + +### API scopes + +- Use least-privilege OAuth scopes. +- Validate scopes on the resource server. +- Validate token audience. +- Avoid overloading scopes with business permissions when more precise checks are needed. +- Avoid broad, long-lived API tokens. +- Show token creation time, last-used time, scope, and expiration to users. +- Allow token revocation. + +## Input Validation + +### General + +- Validate all data crossing a trust boundary: + - HTTP request bodies + - query strings + - route parameters + - headers + - cookies + - uploaded files + - webhook payloads + - queue messages + - imported CSV/JSON/XML + - third-party API responses + - browser storage + - postMessage events + - WebSocket messages +- Validate type, shape, size, format, range, encoding, and semantic constraints. +- Reject unexpected fields when strict schemas are appropriate. +- Normalize before validation when canonical form matters. +- Avoid regex-only validation for complex structured formats when parsers exist. +- Use schema validation libraries for structured input. +- Keep validation close to trust boundaries. +- Revalidate on the server even if the client validates. +- Use typed domain objects after validation to avoid passing raw input deeply through the system. + +### Web form validation + +- Use semantic HTML input types for baseline validation and UX: + - `type="email"` + - `type="url"` + - `type="number"` + - `type="date"` + - `type="tel"` + - `type="password"` +- Use `required`, `minlength`, `maxlength`, `min`, `max`, `step`, and `pattern` where appropriate. +- Do not rely on browser validation for security. +- Treat client validation as convenience only. +- Give generic error messages for sensitive flows. + +### Size limits + +- Enforce maximum request body size. +- Enforce maximum file size. +- Enforce maximum number of uploaded files. +- Enforce maximum string length. +- Enforce maximum array length. +- Enforce pagination limits. +- Enforce recursion/depth limits for nested input. +- Enforce timeouts for parsing and processing. +- Avoid unbounded memory allocation from user input. + +## Output Encoding and Injection Prevention + +## Cross-Site Scripting + +### General XSS prevention + +- Treat HTML, SVG, MathML, CSS, JavaScript, URLs, markdown, and rich text as separate output contexts. +- Use framework escaping by default. +- Avoid raw HTML rendering APIs unless absolutely necessary. +- Sanitize user-authored rich HTML with a proven sanitizer. +- Sanitize on the server for stored rich content where possible. +- Encode untrusted data for the exact output context. +- Never concatenate untrusted input into HTML strings. +- Never concatenate untrusted input into JavaScript source. +- Never concatenate untrusted input into CSS. +- Never put untrusted input into event-handler attributes. +- Never put untrusted input into `javascript:` URLs. +- Validate and normalize URLs before rendering them as links. +- Use Content Security Policy as defense in depth, not as the only XSS control. + +### Dangerous browser APIs + +Avoid or tightly control: + +```js +element.innerHTML = userInput; +element.outerHTML = userInput; +element.insertAdjacentHTML("beforeend", userInput); +document.write(userInput); +eval(userInput); +new Function(userInput); +setTimeout(userInput); +setInterval(userInput); +``` + +Prefer: + +```js +element.textContent = userInput; +element.setAttribute("title", userInput); +const node = document.createTextNode(userInput); +``` + +For URLs: + +```js +const url = new URL(input, location.origin); + +if (!["https:", "http:"].includes(url.protocol)) { + throw new Error("Unsupported URL scheme"); +} + +link.href = url.href; +``` + +For templates, rely on framework escaping: + +```jsx +<p>{displayName}</p> +``` + +Avoid: + +```jsx +<div dangerouslySetInnerHTML={{ __html: userContent }} /> +``` + +If rich HTML is required, sanitize first with a mature sanitizer and still apply CSP. + +### Trusted Types + +- Use Trusted Types where available to reduce DOM XSS risk. +- Enforce `require-trusted-types-for 'script'` in CSP where practical. +- Create narrow Trusted Types policies. +- Do not create a permissive policy that simply returns arbitrary strings. + +Example conceptually: + +```js +const policy = trustedTypes.createPolicy("app-html", { + createHTML(input) { + return sanitizeHtml(input); + }, +}); + +element.innerHTML = policy.createHTML(userHtml); +``` + +### DOM clobbering + +- Do not rely on global variables implicitly created from element IDs or names. +- Use scoped DOM queries. +- Avoid user-controlled `id` and `name` attributes in sensitive DOM areas. +- Avoid reading security-sensitive values from ambiguous DOM properties. + +### Link security + +For links opening a new tab: + +```html +<a href="https://example.com" target="_blank" rel="noopener noreferrer"> + Example +</a> +``` + +- Use `rel="noopener"` to prevent tabnabbing. +- Use `rel="noreferrer"` when referrer leakage is undesirable. +- Validate user-provided links. +- Consider adding `rel="nofollow ugc"` for user-generated external links. + +### Markdown + +- Treat markdown as untrusted input. +- Disable raw HTML unless explicitly needed. +- Sanitize rendered HTML. +- Validate link protocols. +- Validate image sources. +- Avoid allowing arbitrary embedded iframes, scripts, styles, or event handlers. + +## SQL and Database Security + +### SQL injection prevention + +- Use parameterized queries or ORM query binding. +- Never concatenate user input into SQL. +- Never interpolate user input into raw SQL strings. +- Use query builder APIs carefully; raw fragments still need parameterization. +- Validate dynamic identifiers such as column names, table names, and sort directions with allowlists. +- Avoid exposing arbitrary filters that map directly to SQL without validation. +- Use least-privilege database accounts. +- Separate read and write privileges where useful. +- Avoid database superuser credentials in application code. + +Example: + +```js +await db.query("SELECT * FROM users WHERE id = ?", [userId]); +``` + +Avoid: + +```js +await db.query(`SELECT * FROM users WHERE id = ${userId}`); +``` + +Dynamic sort allowlist: + +```js +const allowedSorts = new Set(["created_at", "name", "email"]); +const sort = allowedSorts.has(input.sort) ? input.sort : "created_at"; +const direction = input.direction === "asc" ? "asc" : "desc"; +``` + +### ORM usage + +- Do not assume ORM use automatically prevents all injection. +- Treat raw queries as dangerous. +- Check generated query semantics for authorization and tenant scoping. +- Avoid mass assignment by explicitly selecting allowed fields. +- Avoid leaking sensitive columns through default model serialization. +- Use transactions for multi-step mutations that must be atomic. + +### NoSQL injection + +- Validate JSON query objects. +- Do not pass request bodies directly into database query APIs. +- Prevent operator injection such as `$ne`, `$gt`, `$where`, or equivalent query operators where user data should be scalar. +- Use schema validation to ensure expected primitive values. + +Example: + +```js +const email = String(req.body.email); +await users.findOne({ email }); +``` + +Avoid: + +```js +await users.findOne(req.body); +``` + +## Command Injection and Process Execution + +- Avoid shell execution with user input. +- Prefer native APIs over shell commands. +- If process execution is required, use argument arrays instead of shell strings. +- Do not pass untrusted input to a shell. +- Validate executable names and subcommands with allowlists. +- Set timeouts. +- Limit output size. +- Run with least privilege. +- Use controlled working directories. +- Avoid inheriting sensitive environment variables. +- Avoid logging full command lines if they include secrets. + +Prefer: + +```js +spawn("git", ["status", "--short"], { shell: false }); +``` + +Avoid: + +```js +exec(`git ${userArg}`); +``` + +## Path Traversal and Filesystem Security + +- Treat filenames, paths, archive entries, and MIME types as untrusted. +- Do not concatenate user input into filesystem paths without normalization and containment checks. +- Resolve paths against an allowed base directory. +- Verify the resolved path remains inside the allowed base. +- Reject absolute paths where not expected. +- Reject `..` traversal where not expected. +- Use generated storage names instead of user-supplied filenames. +- Store original filenames only as metadata after sanitization. +- Avoid serving uploaded files from executable locations. +- Avoid following symlinks unless intentional. +- Set restrictive file permissions. +- Use temporary directories safely. +- Clean up temporary files. + +Example: + +```js +const base = path.resolve(uploadRoot); +const target = path.resolve(base, userPath); + +if (!target.startsWith(base + path.sep)) { + throw new Error("Invalid path"); +} +``` + +Also account for case-insensitive filesystems and platform path separators where relevant. + +## File Uploads + +- Require authentication for sensitive uploads. +- Enforce file size limits. +- Enforce file count limits. +- Validate file extension and content type. +- Do not trust the browser-provided MIME type. +- Inspect file signatures where appropriate. +- Store files outside the web root where possible. +- Generate server-side filenames. +- Scan high-risk uploads for malware where appropriate. +- Strip metadata from images or documents where privacy matters. +- Re-encode images when possible to neutralize embedded payloads. +- Serve user-uploaded files with safe headers: + - `Content-Type` + - `Content-Disposition: attachment` where appropriate + - `X-Content-Type-Options: nosniff` + - restrictive CSP for user-content domains +- Use a separate domain or origin for untrusted user content where possible. +- Prevent SVG uploads from executing scripts unless sanitized or served as attachment. +- Be careful with HTML, SVG, PDF, Office documents, archives, and media files. + +### Archive extraction + +- Defend against Zip Slip path traversal. +- Limit extracted size. +- Limit file count. +- Limit nesting depth. +- Reject absolute paths and traversal paths. +- Handle symlinks carefully. +- Detect compression bombs. +- Extract in isolated temporary directories. + +## Cross-Site Request Forgery + +- Use `SameSite=Lax` or `SameSite=Strict` cookies where possible. +- Use CSRF tokens for state-changing requests authenticated by cookies. +- Validate CSRF tokens server-side. +- Bind CSRF tokens to the user session. +- Use custom headers for AJAX requests as an additional barrier. +- Do not use GET for state-changing operations. +- Check `Origin` and `Referer` headers for sensitive state-changing requests, especially where CSRF tokens are difficult. +- Do not rely only on CORS for CSRF protection. +- Bearer-token APIs that do not use ambient cookies are less exposed to classic CSRF, but still need XSS protection. + +## CORS + +- Do not use `Access-Control-Allow-Origin: *` with credentials. +- Avoid reflecting arbitrary `Origin` values. +- Use an allowlist of trusted origins. +- Validate scheme, host, and port exactly. +- Keep allowed methods minimal. +- Keep allowed headers minimal. +- Understand that CORS is a browser access control, not server-side authorization. +- Non-browser clients can ignore CORS. +- Preflight success must not imply authorization. +- Avoid exposing sensitive headers unless needed. + +Example: + +```http +Access-Control-Allow-Origin: https://app.example.com +Access-Control-Allow-Credentials: true +Vary: Origin +``` + +## Content Security Policy + +- Use CSP to reduce XSS impact. +- Prefer nonce- or hash-based scripts over broad host allowlists. +- Avoid `'unsafe-inline'`. +- Avoid `'unsafe-eval'`. +- Use `object-src 'none'`. +- Use `base-uri 'none'` or a narrow value. +- Use `frame-ancestors` to control embedding. +- Use `form-action` to restrict form submission targets. +- Use `upgrade-insecure-requests` where appropriate. +- Use `report-uri` or `report-to` for monitoring during rollout. +- Apply CSP carefully to avoid breaking legitimate scripts. +- Use report-only mode before enforcement for complex apps. + +Example: + +```http +Content-Security-Policy: + default-src 'self'; + script-src 'self' 'nonce-random-value'; + style-src 'self'; + img-src 'self' https: data:; + connect-src 'self' https://api.example.com; + font-src 'self'; + object-src 'none'; + base-uri 'none'; + frame-ancestors 'none'; + form-action 'self'; +``` + +## Security Headers + +Use appropriate headers: + +```http +Strict-Transport-Security: max-age=31536000; includeSubDomains +X-Content-Type-Options: nosniff +Referrer-Policy: strict-origin-when-cross-origin +Permissions-Policy: camera=(), microphone=(), geolocation=() +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Resource-Policy: same-origin +Cross-Origin-Embedder-Policy: require-corp +Content-Security-Policy: ... +``` + +Notes: + +- Use HSTS only over HTTPS. +- Consider HSTS preload only when all subdomains are HTTPS-ready. +- `X-Frame-Options` is older; `frame-ancestors` in CSP is more expressive. Using both may be acceptable for legacy compatibility. +- `X-XSS-Protection` is obsolete and should generally not be relied on. +- `Permissions-Policy` should disable powerful browser features by default. +- COOP/COEP/CORP can help with cross-origin isolation but may require resource changes. + +## HTTPS and Transport Security + +- Use HTTPS everywhere. +- Redirect HTTP to HTTPS. +- Use secure TLS configuration. +- Keep certificates renewed. +- Use HSTS. +- Do not load active mixed content. +- Use secure websocket URLs: `wss://`. +- Do not send secrets over plaintext HTTP. +- Avoid disabling certificate validation in production. +- Pinning is risky operationally and should be used only with strong justification. + +## Browser Storage + +- Treat browser storage as attacker-readable if XSS occurs. +- Avoid storing sensitive long-lived secrets in: + - `localStorage` + - `sessionStorage` + - IndexedDB + - Cache Storage + - non-HttpOnly cookies +- Prefer `HttpOnly`, `Secure`, `SameSite` cookies for sessions. +- Clear sensitive local state on logout. +- Do not store passwords, recovery codes, private keys, or high-value tokens in browser storage unless the design explicitly accounts for compromise. +- Be careful with service worker caches and authenticated responses. +- Avoid caching sensitive pages unless intended. + +## Fetch and Network APIs + +### Fetch + +- Check response status explicitly. +- Use `credentials` intentionally. +- Avoid sending credentials cross-origin unless required. +- Include CSRF tokens where needed. +- Set appropriate `Content-Type`. +- Use `AbortController` for timeouts and cancellation. +- Avoid logging full request or response bodies if they may contain secrets. + +Example: + +```js +const controller = new AbortController(); +const timeout = setTimeout(() => controller.abort(), 10_000); + +try { + const response = await fetch("/api/profile", { + method: "POST", + credentials: "same-origin", + headers: { + "Content-Type": "application/json", + "CSRF-Token": csrfToken, + }, + body: JSON.stringify(payload), + signal: controller.signal, + }); + + if (!response.ok) { + throw new Error(`Request failed: ${response.status}`); + } + + return await response.json(); +} finally { + clearTimeout(timeout); +} +``` + +### URL construction + +- Use `URL` and `URLSearchParams` instead of string concatenation. + +```js +const url = new URL("/search", location.origin); +url.searchParams.set("q", query); +url.searchParams.set("page", String(page)); +``` + +### postMessage + +- Always validate `event.origin`. +- Validate `event.source` when relevant. +- Validate message shape. +- Do not use `targetOrigin: "*"` when sending sensitive data. +- Avoid sending secrets through `postMessage`. + +```js +window.addEventListener("message", (event) => { + if (event.origin !== "https://trusted.example.com") return; + + if (!isExpectedMessage(event.data)) return; + + handleMessage(event.data); +}); +``` + +## Web Workers, Service Workers, and Cache + +### Web workers + +- Validate messages sent to workers. +- Avoid passing secrets unnecessarily. +- Use structured clone safely. +- Be careful with object URLs and imported scripts. +- Restrict worker sources through CSP. + +### Service workers + +- Treat service workers as powerful persistent code. +- Keep service worker scope narrow. +- Avoid caching authenticated or sensitive responses unintentionally. +- Version caches. +- Clear old caches. +- Validate cached response freshness. +- Avoid serving stale security-sensitive shell HTML after logout. +- Be careful with offline behavior for private data. + +### Cache API + +- Do not cache responses containing secrets unless explicitly designed. +- Respect `Cache-Control`. +- Use `Vary` correctly for authenticated or localized content. +- Avoid mixing users in shared caches. +- Include authorization state in cache keys only with care. + +## Cookies + +- Use `HttpOnly` for cookies that JavaScript does not need. +- Use `Secure` for all session cookies. +- Use `SameSite=Lax` or `Strict` where possible. +- Use `SameSite=None; Secure` only when cross-site cookies are truly required. +- Use the `__Host-` prefix for strong host-only secure cookies where possible: + - must be `Secure` + - must have `Path=/` + - must not have `Domain` +- Use the `__Secure-` prefix for secure cookies where appropriate. +- Keep cookie values small. +- Avoid storing sensitive data directly in cookies unless encrypted and integrity-protected. +- Sign cookies that carry trusted data. +- Prefer opaque session IDs over large client-side session payloads. +- Rotate session cookies after authentication changes. + +## Cryptography + +### General + +- Do not design custom cryptographic algorithms. +- Do not implement crypto primitives manually. +- Use platform crypto APIs or audited libraries. +- Use authenticated encryption. +- Use secure random number generation. +- Separate keys by purpose. +- Rotate keys when needed. +- Protect keys in a key management system where possible. +- Avoid hardcoded keys. +- Avoid reusing nonces/IVs with algorithms that require uniqueness. +- Avoid obsolete algorithms: + - MD5 + - SHA-1 for collision-resistant uses + - DES + - 3DES + - RC4 + - ECB mode +- Use constant-time comparison for secrets where timing attacks matter. + +### Browser crypto + +Use Web Crypto API: + +```js +const bytes = new Uint8Array(32); +crypto.getRandomValues(bytes); +``` + +For UUIDs: + +```js +const id = crypto.randomUUID(); +``` + +Prefer Web Crypto for hashing, signing, key derivation, and encryption in browser contexts when needed: + +```js +const data = new TextEncoder().encode("message"); +const digest = await crypto.subtle.digest("SHA-256", data); +``` + +Notes: + +- `btoa` and `atob` are encoding helpers, not encryption. +- Base64 is not encryption. +- Hashing is not encryption. +- Encryption without authentication is usually insufficient. +- Client-side encryption does not protect data from malicious client code running in the same origin. + +### Randomness + +- Use `crypto.getRandomValues()` in browsers. +- Use `crypto.randomUUID()` for UUIDs where appropriate. +- Use server-side cryptographic randomness for tokens. +- Do not use `Math.random()` for security-sensitive values. + +Avoid: + +```js +const token = Math.random().toString(36).slice(2); +``` + +Prefer: + +```js +const token = crypto.randomUUID(); +``` + +or random bytes from a cryptographic RNG. + +## Secrets Management + +- Do not commit secrets to source control. +- Do not hardcode API keys, passwords, private keys, or tokens. +- Use environment variables, secret managers, or platform secret stores. +- Keep secrets out of logs, stack traces, metrics, URLs, analytics, and error reports. +- Rotate leaked secrets immediately. +- Scope secrets narrowly. +- Use separate secrets per environment. +- Avoid sharing production secrets with development or CI unnecessarily. +- Audit access to secrets. +- Use short-lived credentials where possible. +- Prefer workload identity or managed credentials over static keys where available. +- Add secret scanning in CI and repository hosting where possible. + +## Error Handling + +- Return generic errors to users for sensitive flows. +- Log enough detail server-side to diagnose issues. +- Do not expose stack traces in production responses. +- Do not expose internal paths, SQL, config, environment variables, dependency versions, or secrets. +- Avoid reflecting raw user input in error pages. +- Normalize authentication errors: + - “Invalid email or password” + - not “No account found” +- Use structured errors internally. +- Map internal errors to safe external responses. +- Preserve causal information in logs without leaking it to clients. + +## Logging and Monitoring + +- Log security-relevant events: + - login success/failure + - logout + - password reset requested/completed + - MFA changes + - email changes + - permission changes + - admin actions + - token creation/revocation + - suspicious rate-limit activity + - access denied events + - webhook validation failures +- Avoid logging secrets or raw sensitive payloads. +- Redact: + - passwords + - tokens + - authorization headers + - cookies + - API keys + - reset links + - private keys + - payment data + - sensitive personal data +- Use structured logs. +- Include request IDs or correlation IDs. +- Protect logs with access control. +- Define retention periods. +- Monitor anomalies. +- Alert on high-risk events. +- Avoid making logs a secondary data leak. + +## Rate Limiting and Abuse Protection + +- Rate limit authentication endpoints. +- Rate limit password reset and email verification. +- Rate limit token creation. +- Rate limit expensive endpoints. +- Rate limit by user, IP, account, tenant, API key, and device where appropriate. +- Use progressive delays or lockouts carefully to avoid account lockout abuse. +- Add bot defenses where needed. +- Avoid disclosing which part of authentication failed. +- Add quotas for resource creation, export, uploads, and invitations. +- Protect search endpoints from enumeration and scraping. +- Protect webhook receivers from replay and flooding. + +## Webhooks + +- Verify webhook signatures. +- Verify timestamp freshness. +- Prevent replay attacks. +- Use raw request body for signature verification when required by provider. +- Use constant-time comparison for signatures. +- Validate event type and payload schema. +- Make webhook processing idempotent. +- Store processed event IDs. +- Return success only after durable acceptance or clear processing semantics. +- Do not trust webhook payloads solely because they come from a known URL. +- Fetch authoritative state from provider APIs for high-risk actions when appropriate. + +## SSRF + +- Treat user-provided URLs as dangerous. +- Parse URLs with a real URL parser. +- Allowlist schemes, hosts, and ports. +- Block private, loopback, link-local, multicast, and metadata IP ranges. +- Resolve DNS carefully and defend against DNS rebinding. +- Re-check IP after redirects. +- Limit redirects. +- Set request timeouts. +- Limit response size. +- Avoid sending internal credentials to fetched URLs. +- Do not allow arbitrary headers. +- Disable access to cloud metadata endpoints. +- Use egress firewalling where possible. + +Dangerous examples: + +```text +http://localhost +http://127.0.0.1 +http://169.254.169.254 +http://[::1] +file:///etc/passwd +``` + +## Open Redirects + +- Avoid redirecting to arbitrary user-provided URLs. +- Use relative redirects where possible. +- Use allowlists for external redirects. +- Parse and normalize URLs before checking. +- Reject protocol-relative URLs such as `//evil.example`. +- Do not trust URLs just because they start with a substring. + +Prefer: + +```js +const allowed = new Set(["/dashboard", "/settings"]); +const next = allowed.has(inputNext) ? inputNext : "/dashboard"; +``` + +## Clickjacking and Framing + +- Prevent unwanted framing with CSP: + +```http +Content-Security-Policy: frame-ancestors 'none' +``` + +or allow specific parents: + +```http +Content-Security-Policy: frame-ancestors 'self' https://partner.example +``` + +- Use `X-Frame-Options: DENY` or `SAMEORIGIN` for older compatibility where appropriate. +- For embeddable apps, design explicit framing policy. +- Use frame-busting scripts only as weak defense in depth. + +## HTML and Form Security + +- Use proper form methods. +- Do not use GET for sensitive or state-changing submissions. +- Avoid putting secrets in hidden fields unless integrity-protected and non-sensitive. +- Server-side validate all hidden fields. +- Protect state-changing forms with CSRF defenses. +- Set `autocomplete` intentionally. +- Use `inputmode` for UX, not security. +- Use `readonly`/`disabled` only for UX, not trust. +- Do not trust client-side calculated totals, prices, roles, or permissions. + +## Iframes and Embedding + +- Use `sandbox` for untrusted iframe content. +- Add only the permissions required. +- Use `allow` for specific capabilities. +- Use a separate origin for untrusted embedded content. +- Avoid allowing both `allow-scripts` and `allow-same-origin` for untrusted content unless fully understood. +- Validate `postMessage` communication. + +Example: + +```html +<iframe + src="https://usercontent.example.com/document/123" + sandbox="allow-scripts" + referrerpolicy="no-referrer" +></iframe> +``` + +## Permissions and Powerful Browser APIs + +- Request browser permissions only when needed. +- Explain permission use in product UX. +- Handle denied permissions gracefully. +- Avoid requesting permissions on page load. +- Use `Permissions-Policy` to disable unused capabilities. +- Be careful with: + - geolocation + - camera + - microphone + - clipboard + - notifications + - USB + - Bluetooth + - serial + - file system access + - payment APIs + - idle detection +- Do not collect more data than necessary. +- Do not keep permission-derived data longer than needed. + +## Clipboard + +- Treat clipboard content as untrusted input. +- Avoid writing sensitive data to the clipboard without user action. +- Avoid reading clipboard without clear user intent. +- Sanitize pasted rich text. +- Prefer plain text paste in sensitive fields where appropriate. + +## File System Access API + +- Use only in secure contexts. +- Require explicit user gestures. +- Treat file contents as untrusted. +- Validate file type, size, and content. +- Do not assume a previously granted handle remains valid. +- Avoid writing files without clear user intent. +- Handle permission denial. + +## Subresource Integrity + +- Use SRI for third-party scripts and styles when loading from CDNs. + +```html +<script + src="https://cdn.example.com/library.js" + integrity="sha384-..." + crossorigin="anonymous" +></script> +``` + +- Pin exact versions. +- Avoid loading critical code from third-party CDNs when self-hosting is practical. +- Combine SRI with CSP. + +## Dependency Security + +- Keep dependencies updated. +- Remove unused dependencies. +- Prefer widely used, maintained libraries. +- Review dependency health: + - maintenance activity + - known vulnerabilities + - transitive dependencies + - install scripts + - package ownership changes +- Pin versions or use lockfiles. +- Use automated vulnerability scanning. +- Use software composition analysis where appropriate. +- Avoid running untrusted package scripts in sensitive environments. +- Review major upgrades. +- Avoid libraries for trivial functionality. +- Watch for dependency confusion and typosquatting. +- Use private package scopes and registry configuration carefully. + +## Supply Chain and Build Security + +- Use lockfiles. +- Build from clean environments. +- Protect CI secrets. +- Do not expose secrets to untrusted pull requests. +- Use least-privilege CI tokens. +- Pin GitHub Actions or third-party CI actions to trusted versions or SHAs where appropriate. +- Separate build, test, release, and deployment permissions. +- Sign artifacts where appropriate. +- Generate SBOMs where useful. +- Verify provenance for high-assurance systems. +- Do not run production deployments from unreviewed branches. +- Protect release branches. +- Require reviews for sensitive code paths. +- Use reproducible builds where practical. + +## Environment Configuration + +- Use secure defaults. +- Make development-only features impossible to enable accidentally in production. +- Validate required environment variables at startup. +- Fail startup if critical security config is missing. +- Separate dev, staging, and production environments. +- Use different databases and credentials per environment. +- Disable debug mode in production. +- Disable verbose error pages in production. +- Do not expose admin/debug endpoints publicly. +- Protect health and metrics endpoints if they disclose sensitive information. + +## API Design Security + +- Use consistent authentication and authorization middleware. +- Use schema validation for requests and responses. +- Use explicit response DTOs. +- Avoid returning whole database objects. +- Avoid exposing internal IDs if enumeration risk matters. +- Use pagination on list endpoints. +- Put maximum page sizes in place. +- Use idempotency keys for retryable mutations such as payments or order creation. +- Validate content type. +- Reject unknown content types. +- Use consistent error shapes. +- Use appropriate HTTP status codes. +- Avoid overly detailed errors for sensitive actions. +- Version APIs deliberately. +- Document security semantics. + +## GraphQL Security + +- Authorize at resolver or data-access level. +- Prevent cross-tenant object access. +- Limit query depth. +- Limit query complexity. +- Limit aliases and batching. +- Disable introspection in production if schema secrecy is required, though authorization remains essential. +- Avoid exposing sensitive fields by default. +- Prevent N+1 queries that can become DoS vectors. +- Use persisted queries for public APIs where appropriate. +- Validate file uploads carefully if supported. + +## WebSocket and Realtime Security + +- Authenticate websocket connections. +- Authorize each subscription, channel, room, and action. +- Revalidate permissions on reconnect. +- Do not trust client-supplied room IDs. +- Validate message schemas. +- Rate limit messages. +- Limit message size. +- Handle backpressure. +- Do not broadcast sensitive data to unauthorized subscribers. +- Clean up subscriptions on disconnect. +- Use `wss://` in production. +- Check `Origin` where browser websocket CSRF-like risks matter. + +## Server-Sent Events + +- Authenticate streams. +- Authorize stream resources. +- Avoid leaking cross-tenant events. +- Handle disconnects. +- Avoid unbounded buffering. +- Use correct cache headers. +- Consider token/session expiry behavior for long-lived connections. + +## Data Protection and Privacy + +- Collect only necessary data. +- Store only necessary data. +- Retain data only as long as needed. +- Classify sensitive data. +- Encrypt sensitive data at rest where appropriate. +- Use field-level encryption for especially sensitive values where appropriate. +- Avoid putting sensitive data in URLs. +- Avoid putting sensitive data in browser history. +- Avoid sending sensitive data to third-party analytics. +- Mask sensitive values in UI where appropriate. +- Provide secure deletion semantics where required. +- Use access logs and audit logs for sensitive data access. +- Separate operational logs from sensitive data stores. +- Consider privacy laws and consent requirements. + +## Payment and Financial Data + +- Use hosted payment pages or tokenized payment providers where possible. +- Avoid handling raw card data unless necessary and compliant. +- Never log card numbers, CVV, bank credentials, or payment tokens. +- Use idempotency keys for payment operations. +- Verify webhook signatures. +- Reconcile payment status from provider APIs. +- Treat client-reported payment success as untrusted. +- Enforce server-side price, currency, discount, tax, and ownership checks. +- Avoid trusting cart totals from the client. + +## Email Security + +- Do not put sensitive secrets in email when avoidable. +- Make reset and verification links single-use and time-limited. +- Avoid account enumeration. +- Use generic copy for sensitive flows. +- Sign outbound mail with SPF, DKIM, and DMARC at the domain level. +- Avoid user-controlled HTML injection in emails. +- Sanitize or escape user-provided email content. +- Avoid open redirects in email links. +- Make unsubscribe links unguessable and scoped. + +## OAuth and OIDC + +- Use Authorization Code flow with PKCE for browser/mobile clients. +- Validate `state` to prevent CSRF. +- Validate `nonce` for ID tokens. +- Validate issuer, audience, expiration, signature, and algorithm. +- Do not use implicit flow for new applications. +- Store OAuth client secrets only on confidential clients. +- Never expose client secrets in browser code. +- Use exact redirect URI matching. +- Avoid wildcard redirect URIs. +- Keep scopes minimal. +- Treat provider profile data as untrusted until mapped and validated. +- Handle account linking carefully to prevent account takeover. +- Reconfirm sensitive account linking operations. + +## Passkeys and WebAuthn + +- Prefer WebAuthn/passkeys for phishing-resistant authentication. +- Validate challenge, origin, RP ID, user presence, and user verification requirements. +- Use server-generated challenges. +- Expire challenges. +- Prevent challenge replay. +- Store credential public keys and counters. +- Handle backup eligible credentials and synced passkeys according to product risk. +- Allow multiple credentials per account. +- Provide secure recovery flows. + +## Frontend Framework Security + +### General + +- Rely on framework escaping. +- Avoid raw HTML escape hatches: + - React `dangerouslySetInnerHTML` + - Vue `v-html` + - Angular `bypassSecurityTrust...` + - Svelte `{@html ...}` +- Sanitize any user-controlled HTML before using escape hatches. +- Avoid template injection from user-controlled template strings. +- Avoid dynamic component loading from untrusted names. +- Avoid rendering untrusted JSON directly into inline scripts without safe escaping. +- Avoid hydration data XSS by escaping `<`, `>`, `&`, U+2028, and U+2029 where relevant. +- Keep framework and build tooling updated. + +### React + +- JSX escapes text by default. +- Avoid `dangerouslySetInnerHTML`. +- Validate URLs used in `href`, `src`, and navigation APIs. +- Avoid putting secrets in client-side bundles. +- Do not rely on client-side route guards. +- Be careful with server components and server actions: validate and authorize server-side. + +### Vue + +- Mustaches escape by default. +- Avoid `v-html` with untrusted content. +- Validate dynamic arguments and URLs. +- Do not mount Vue on DOM that contains user-provided server-rendered HTML. + +### Angular + +- Angular sanitizes many contexts by default. +- Avoid bypass APIs unless absolutely necessary. +- Treat `DomSanitizer.bypassSecurityTrust...` as security-sensitive. +- Validate route guards server-side too. + +### Svelte + +- Text interpolation escapes by default. +- Avoid `{@html}` with untrusted content. +- Validate URLs and dynamic attributes. + +## Server Rendering and Hydration + +- Escape serialized data embedded in HTML. +- Do not embed raw JSON with unescaped `</script>`. +- Use safe serialization libraries or framework helpers. +- Avoid exposing server-only environment variables to the client. +- Distinguish public build-time config from private server config. +- Do not leak stack traces or server internals through SSR error pages. +- Validate and authorize server actions, loaders, and mutations. +- Avoid caching private SSR pages as public content. + +## HTTP Caching + +- Use `Cache-Control: no-store` for highly sensitive responses. +- Use `private` for user-specific cacheable responses. +- Use `Vary: Authorization` or avoid shared caching for authenticated content. +- Avoid caching pages with CSRF tokens in shared caches. +- Avoid exposing one user’s data to another through CDN or proxy caches. +- Use cache keys that include tenant, locale, auth state, and other relevant variants. +- Be careful with stale responses after permission changes or logout. + +Example: + +```http +Cache-Control: no-store +``` + +For user-specific but browser-cacheable data: + +```http +Cache-Control: private, max-age=60 +``` + +## XML Security + +- Avoid XML when JSON is sufficient. +- Disable external entity resolution. +- Disable DTDs where possible. +- Protect against XXE. +- Limit document size and nesting. +- Use safe XML parsers. +- Avoid XPath injection by parameterizing or validating expressions. +- Be careful with XML signatures and canonicalization. + +## JSON Security + +- Validate schema. +- Limit body size. +- Avoid parsing untrusted JSON with custom evaluators. +- Use `JSON.parse`, not `eval`. +- Avoid JSONP for modern apps. +- Set correct content type: + +```http +Content-Type: application/json +``` + +- Avoid serving sensitive JSON to unauthenticated contexts. +- Consider JSON hijacking only for legacy browser threat models; modern mitigations generally include proper auth, content type, and no JSONP. + +## Deserialization + +- Avoid deserializing untrusted data into executable objects. +- Avoid formats that can instantiate arbitrary classes from untrusted input. +- Prefer JSON with schema validation. +- Disable polymorphic deserialization unless tightly controlled. +- Avoid `pickle`, native object serialization, or equivalent unsafe mechanisms with untrusted input. +- Sign and validate serialized tokens if they carry trusted state. +- Encrypt only if confidentiality is needed; still authenticate/integrity-protect. + +## Template Injection + +- Do not compile templates from user input. +- Do not pass untrusted values as template source. +- Use template variables, not string concatenation. +- Avoid exposing powerful helpers or global objects to templates. +- Sandbox user-authored templates if the product requires them. +- Prefer restricted expression languages. + +## Regex Security + +- Avoid vulnerable regexes with catastrophic backtracking. +- Limit input length before regex matching. +- Prefer simple regexes. +- Use parser libraries for complex languages. +- Consider regex engines with guaranteed linear time where available. +- Avoid using user-provided regexes directly. +- Time-limit expensive matching if user-provided patterns are a feature. + +## Prototype Pollution + +In JavaScript: + +- Treat object keys from untrusted input carefully. +- Reject keys such as: + - `__proto__` + - `prototype` + - `constructor` +- Prefer `Object.create(null)` for dictionaries when appropriate. +- Use `Map` for arbitrary key-value maps. +- Avoid unsafe deep merge of untrusted objects. +- Keep libraries updated. + +Example: + +```js +const data = new Map(); +data.set(userKey, value); +``` + +## JavaScript Language and API Practices + +### Safer modern syntax + +- Use `const` by default. +- Use `let` when reassignment is required. +- Avoid `var`. +- Use strict equality `===` and `!==`. +- Use optional chaining deliberately: + +```js +const city = user.profile?.address?.city; +``` + +- Use nullish coalescing for defaulting only on `null` or `undefined`: + +```js +const limit = input.limit ?? 20; +``` + +- Avoid `||` defaults when `0`, `false`, or `""` are valid. +- Use destructuring carefully and validate before destructuring untrusted nested data. +- Use modules instead of globals. +- Avoid implicit globals. +- Avoid monkey-patching built-ins. +- Prefer pure functions for validation and authorization checks. +- Keep security-sensitive branches explicit. + +### Type checks + +```js +if (typeof value !== "string") { + throw new Error("Expected string"); +} + +if (!Array.isArray(items)) { + throw new Error("Expected array"); +} +``` + +- Remember `typeof null === "object"`. +- Validate dates with care. +- Avoid trusting TypeScript types at runtime. +- Use runtime validation at external boundaries. + +### Objects + +- Use `Object.hasOwn()` instead of direct `obj.hasOwnProperty()` where available: + +```js +if (Object.hasOwn(record, key)) { + // ... +} +``` + +- Avoid iterating inherited properties accidentally. +- Use `Object.freeze()` for immutable constants when useful. +- Use `Map` for dynamic keys. + +### Arrays + +- Use `Array.prototype.at()` for readable relative indexing: + +```js +const last = items.at(-1); +``` + +- Avoid mutating arrays unexpectedly in shared state. +- Be aware of newer non-mutating methods where supported: + - `toSorted()` + - `toReversed()` + - `toSpliced()` + - `with()` + +```js +const sorted = items.toSorted((a, b) => a.name.localeCompare(b.name)); +``` + +### Promises and async + +- Always handle rejected promises. +- Use `try`/`catch` around awaited operations. +- Avoid floating promises unless intentionally detached and observed. +- Use `Promise.all` for independent operations. +- Use `Promise.allSettled` when partial failure is expected. +- Use `AbortController` for cancellation. +- Avoid swallowing errors silently. +- Avoid exposing raw errors to clients. + +### Dates and time + +- Store timestamps in UTC. +- Use ISO 8601 strings for interchange. +- Do not trust client clocks for security decisions. +- Use server time for expiration, token validity, and audit events. +- Be careful with time zones, DST, and date-only values. +- Use mature date/time libraries when requirements are complex. +- The Temporal API is desirable where available or polyfilled, but compatibility must be considered. + +## TypeScript Security and Clean Code + +- Use `strict` mode. +- Avoid `any` for external input. +- Represent untrusted input as `unknown` until validated. +- Use discriminated unions for state machines and variants. +- Use branded types for validated IDs, tenant IDs, or sanitized HTML where helpful. +- Avoid type assertions that skip validation. +- Avoid non-null assertions in security-sensitive code. +- Keep DTOs separate from database models. +- Do not expose sensitive fields through broad types. +- Use exhaustive checks. + +Example: + +```ts +function assertNever(value: never): never { + throw new Error(`Unexpected value: ${String(value)}`); +} +``` + +Use runtime validation: + +```ts +function parseUserId(value: unknown): string { + if (typeof value !== "string" || !/^[a-zA-Z0-9_-]+$/.test(value)) { + throw new Error("Invalid user ID"); + } + + return value; +} +``` + +## Clean Code for Security + +- Make trust boundaries obvious. +- Name variables to indicate trust level: + - `rawInput` + - `validatedInput` + - `sanitizedHtml` + - `currentUser` + - `targetResource` +- Keep validation, authorization, and mutation steps clear. +- Avoid mixing parsing, authorization, and side effects in one large function. +- Use small, testable policy functions. +- Centralize repeated security checks. +- Avoid clever control flow in security-sensitive code. +- Prefer explicit conditions over implicit truthiness when security matters. +- Avoid boolean parameter traps for permission-sensitive behavior. +- Avoid global mutable state. +- Keep dependencies injectable for tests, but avoid exposing security-critical internals unnecessarily. +- Write tests for negative cases, not just successful paths. +- Use clear errors internally and safe errors externally. +- Document non-obvious security decisions. + +## Testing Security Behavior + +### Unit and integration tests + +Test: + +- unauthenticated access +- authenticated but unauthorized access +- cross-tenant access +- resource ownership checks +- invalid IDs +- malformed input +- extra fields +- missing fields +- boundary values +- oversized payloads +- expired tokens +- revoked tokens +- replayed webhooks +- CSRF failures +- rate limit behavior +- open redirect attempts +- path traversal attempts +- upload rejection +- XSS payload rendering +- SQL/NoSQL injection attempts where relevant + +### Example negative tests + +- User A cannot access User B’s resource. +- Tenant A cannot access Tenant B’s data. +- Non-admin cannot call admin mutation. +- Disabled account cannot authenticate. +- Expired reset token fails. +- Used reset token fails. +- Invalid webhook signature fails. +- `../secret.txt` file path fails. +- `javascript:alert(1)` link is rejected. +- Unknown JSON fields are rejected where strict schemas are expected. + +### Security regression testing + +- Add regression tests for every fixed vulnerability. +- Test both UI route and direct API access. +- Test authorization at the lowest meaningful layer. +- Include abuse cases in automated tests where feasible. +- Use static analysis and dependency scanning as support, not replacement for review. + +## Secure Defaults in UI + +- Hide controls the user cannot use, but still enforce server authorization. +- Avoid exposing sensitive identifiers or internal state in DOM attributes. +- Avoid embedding secrets in HTML. +- Avoid storing privileged flags in client state as trusted values. +- Disable autocomplete only where there is a clear reason; use correct autocomplete tokens otherwise. +- Prevent accidental destructive actions with confirmation or undo where appropriate. +- Re-authenticate for sensitive operations. +- Show last login/session/device information where useful. +- Provide logout from all sessions where appropriate. + +## Admin Interfaces + +- Require strong authentication and MFA. +- Use explicit authorization for each admin action. +- Audit all admin actions. +- Protect against CSRF. +- Do not expose admin interfaces publicly unless necessary. +- Use network restrictions where appropriate. +- Avoid bulk dangerous actions without confirmation and auditability. +- Separate read-only support access from mutating admin access. +- Mask sensitive user data by default. +- Require justification for sensitive access where appropriate. + +## Internationalization and Unicode Security + +- Normalize strings where identity comparison matters. +- Be careful with Unicode confusables in usernames, domains, slugs, and identifiers. +- Avoid case-folding bugs. +- Validate email and domain names with appropriate libraries. +- Do not assume one character equals one code unit. +- Limit identifier character sets when simplicity is more important than full Unicode flexibility. +- Be careful with bidirectional text in logs, filenames, and rendered content. +- Escape rendered text regardless of language. + +## URL and Domain Handling + +- Use the `URL` API. +- Normalize before comparison. +- Compare hostnames carefully. +- Account for punycode/internationalized domains. +- Validate schemes. +- Reject dangerous schemes: + - `javascript:` + - `data:` unless explicitly allowed + - `file:` + - `blob:` unless explicitly allowed +- Be careful with username/password components in URLs. +- Be careful with trailing dots, mixed case, encoded characters, and redirects. +- Avoid substring checks for domain validation. + +Bad: + +```js +if (url.includes("example.com")) { + redirect(url); +} +``` + +Better: + +```js +const parsed = new URL(url); +if (parsed.origin !== "https://example.com") { + throw new Error("Invalid redirect target"); +} +``` + +## Regular Web Platform APIs + +### Sanitizer API + +- A native Sanitizer API has been discussed and partially implemented in some environments, but compatibility is limited. +- Use it only as progressive enhancement if available and backed by a reliable fallback. +- For production rich-text sanitization, use a mature sanitizer library until native support is universal. + +### URLPattern + +- `URLPattern` can make route and URL matching clearer where available. +- Use feature detection or fallback if targeting environments without it. + +```js +if ("URLPattern" in globalThis) { + const pattern = new URLPattern({ pathname: "/users/:id" }); +} +``` + +### AbortSignal timeout + +Where supported: + +```js +const response = await fetch(url, { + signal: AbortSignal.timeout(10_000), +}); +``` + +Fallback with `AbortController` when unavailable. + +### Structured clone + +- Use `structuredClone()` for cloning structured data where supported. +- Do not use JSON stringify/parse as a general clone for data with dates, maps, sets, undefined, or special values. +- Cloning does not validate trust. + +```js +const copy = structuredClone(value); +``` + +## Secure Data Serialization in HTML + +When embedding JSON in HTML, avoid raw injection: + +Bad: + +```html +<script> + window.__DATA__ = JSON.parse("..."); +</script> +``` + +Risky if not escaped correctly. + +Prefer framework helpers or safe serialization that escapes HTML-significant characters. + +Important characters/sequences to handle include: + +- `<` +- `>` +- `&` +- `</script` +- U+2028 +- U+2029 + +## Memory and Resource Exhaustion + +- Limit request sizes. +- Limit response sizes. +- Limit concurrency. +- Limit expensive operations. +- Add timeouts. +- Add pagination. +- Add quotas. +- Avoid loading entire large files into memory. +- Stream where appropriate. +- Bound queues. +- Use backpressure. +- Protect regex, compression, image processing, PDF processing, and archive extraction. +- Avoid unbounded recursion. + +## Image and Media Processing + +- Treat media files as untrusted. +- Use maintained libraries. +- Keep codecs and processing libraries updated. +- Limit dimensions, duration, frame count, and file size. +- Re-encode images where feasible. +- Strip metadata where privacy matters. +- Avoid processing untrusted media in privileged processes. +- Consider sandboxing for high-risk formats. + +## PDF and Document Handling + +- Treat PDFs and office documents as active content. +- Avoid rendering untrusted documents inline unless needed. +- Serve as attachments where appropriate. +- Use separate origins for inline previews. +- Scan or sandbox high-risk document processing. +- Avoid trusting metadata. +- Limit conversion resources. + +## Business Logic Security + +- Never trust client-side prices, discounts, roles, quotas, balances, or ownership. +- Recompute sensitive business values server-side. +- Use transactions for financial or inventory updates. +- Use idempotency keys for retryable operations. +- Prevent race conditions. +- Enforce state transitions server-side. +- Ensure users cannot skip workflow steps by calling APIs directly. +- Protect invitation, approval, refund, cancellation, and role-change flows. +- Audit sensitive business actions. + +## Race Conditions + +- Use database transactions. +- Use unique constraints. +- Use optimistic locking or version checks. +- Use row-level locks where appropriate. +- Make operations idempotent. +- Avoid check-then-act without atomic enforcement. +- Protect one-time token use atomically. +- Protect inventory, quota, balance, and permission updates. + +## Security Review Checklist + +For code review, I would check: + +- Are all inputs validated? +- Are all outputs encoded or sanitized for their context? +- Are authn/authz checks server-side? +- Are object-level permissions enforced? +- Is tenant isolation preserved? +- Are secrets kept out of code, logs, URLs, and client bundles? +- Are dangerous APIs avoided? +- Are database queries parameterized? +- Are redirects constrained? +- Are file paths contained? +- Are uploads constrained and safely served? +- Are requests rate-limited where abuse-prone? +- Are errors safe externally? +- Are logs useful but redacted? +- Are dependencies reasonable and current? +- Are tests covering failure and abuse cases? +- Does the code fail closed? +- Are security-sensitive assumptions documented? + +## Common Vulnerability Patterns I Would Flag + +- Client-only authorization. +- Missing tenant filter. +- Direct object access without ownership check. +- Raw HTML rendering. +- User-controlled redirect. +- SQL query string interpolation. +- Shell command string interpolation. +- Unsafe file path join. +- Unrestricted file upload. +- Storing tokens in `localStorage`. +- Missing CSRF protection for cookie-authenticated mutation. +- CORS reflection. +- Overly broad CSP or no CSP. +- Detailed auth errors. +- Logging credentials. +- Long-lived unrevocable tokens. +- Trusting webhook payloads without signature verification. +- Missing replay protection. +- Using `Math.random()` for tokens. +- Custom crypto. +- Unsafe deserialization. +- Unbounded request size. +- Unbounded pagination. +- Missing rate limits on auth endpoints. +- Exposing stack traces in production. +- Committing secrets. +- Debug mode in production. +- Admin endpoints with weak controls. +- User-controlled templates. +- Unsafe deep merge causing prototype pollution. +- Insecure dependency or abandoned package. + +## Recommended Modern Browser/API Defaults + +- Use `fetch` with explicit `credentials`, headers, status handling, and abort signals. +- Use `URL` and `URLSearchParams`. +- Use `crypto.randomUUID()` for random UUIDs. +- Use `crypto.getRandomValues()` for random bytes. +- Use `crypto.subtle` for browser cryptography when needed. +- Use `textContent` instead of `innerHTML`. +- Use `structuredClone()` for structured cloning. +- Use `Object.hasOwn()` for own-property checks. +- Use `Map` for arbitrary key dictionaries. +- Use `Set` for membership checks. +- Use `AbortController`. +- Use `FormData` for multipart form submissions. +- Use `input` attributes for UX validation, backed by server validation. +- Use `navigator.clipboard` only with user intent. +- Use `postMessage` with exact origin checks. +- Use `sandbox` on iframes with minimal capabilities. +- Use `rel="noopener noreferrer"` on external new-tab links. +- Use `Referrer-Policy`. +- Use `Permissions-Policy`. +- Use CSP with nonces/hashes where possible. +- Use feature detection for cutting-edge APIs. +- Use progressive enhancement for APIs with uneven support. + +## Progressive Enhancement Guidance + +For newer or unevenly supported web features: + +- Feature-detect before use. +- Provide a fallback. +- Avoid making security depend solely on unsupported client features. +- Use server-side enforcement as the real control. +- Treat new browser security APIs as defense in depth unless support is universal enough for the target audience. + +Examples: + +```js +if ("randomUUID" in crypto) { + id = crypto.randomUUID(); +} else { + id = fallbackUuidFromCryptoRandomValues(); +} +``` + +```js +if ("trustedTypes" in window) { + // Install Trusted Types policy. +} +``` + +```js +if ("URLPattern" in globalThis) { + // Use URLPattern route matching. +} else { + // Use existing router or parser. +} +``` + +## Clean Architecture Defaults for Secure Systems + +- Put authentication state in a clear request context. +- Put authorization in named policies or guards. +- Keep raw request parsing at the edge. +- Convert raw input into validated command/query objects. +- Keep domain logic independent from transport details where practical. +- Keep database access behind functions that require tenant/user context. +- Avoid repositories that can accidentally query globally when tenant scoping is required. +- Use explicit serialization for API responses. +- Keep sensitive fields private by default. +- Use centralized error mapping. +- Use centralized logging redaction. +- Use config validation at startup. +- Keep security middleware order clear and tested. + +## Example Secure Request Flow + +A robust state-changing endpoint generally does this: + +1. Require HTTPS. +2. Authenticate the caller. +3. Parse and validate request body. +4. Enforce CSRF if using cookie authentication. +5. Load the target resource scoped by tenant/user. +6. Authorize the specific action. +7. Enforce business invariants. +8. Perform mutation in a transaction if needed. +9. Emit audit/security event if sensitive. +10. Return an explicit safe response DTO. +11. Avoid leaking internal details on failure. + +## Example Safe Mutation Shape + +```ts +async function updateProjectName(request: RequestContext, rawInput: unknown) { + const input = parseUpdateProjectNameInput(rawInput); + + const project = await projects.findByIdForTenant({ + projectId: input.projectId, + tenantId: request.tenantId, + }); + + if (!project) { + throw new NotFoundError(); + } + + if (!canUpdateProject(request.user, project)) { + throw new NotFoundError(); + } + + const updated = await projects.updateName({ + projectId: project.id, + name: input.name, + }); + + auditLog.record({ + actorId: request.user.id, + action: "project.name_updated", + resourceId: project.id, + }); + + return { + id: updated.id, + name: updated.name, + }; +} +``` + +## Things I Would Not Consider Project-Specific + +The following are broad common knowledge and usually do not need to be repeated in a local project guide unless the project has a special nuance: + +- Validate untrusted input. +- Escape output. +- Do not trust the client. +- Check authorization server-side. +- Use parameterized SQL. +- Avoid `eval`. +- Avoid raw `innerHTML`. +- Use HTTPS. +- Use secure cookies. +- Do not commit secrets. +- Do not log passwords or tokens. +- Use proven crypto libraries. +- Do not use `Math.random()` for tokens. +- Add rate limits to login/reset endpoints. +- Use CSRF protection with cookie-authenticated mutations. +- Validate webhook signatures. +- Do not expose stack traces in production. +- Keep dependencies updated. +- Use least privilege. +- Add tests for negative authorization cases. +- Use `URL` instead of string parsing URLs. +- Use `textContent` for untrusted text. +- Use `rel="noopener"` for new-tab links. +- Use CSP as defense in depth. +- Use `SameSite`, `Secure`, and `HttpOnly` cookies. +- Use server-side enforcement for business rules. diff --git a/.agents/skills/project-discipline-guides/mirrors/security/gemini_mirror.md b/.agents/skills/project-discipline-guides/mirrors/security/gemini_mirror.md new file mode 100644 index 000000000..96759e848 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/security/gemini_mirror.md @@ -0,0 +1,138 @@ +# Comprehensive Web Security Redundancy Mirror + +This guide outlines the standard security best practices, syntax, and APIs for modern web development. It represents the "Common Knowledge" baseline for security implementations in frontend and backend (Node.js) environments. + +--- + +## 1. Transport & Connection Security + +### HTTPS and HSTS +* **Mandatory Encryption:** All traffic must be served over HTTPS. +* **Strict-Transport-Security (HSTS):** Use the `Strict-Transport-Security` header to force browsers to use HTTPS for all future requests. + * *Syntax:* `Strict-Transport-Security: max-age=31536000; includeSubDomains; preload` +* **TLS Configuration:** Disable deprecated protocols (SSLv2, SSLv3, TLS 1.0, TLS 1.1). Prefer TLS 1.3 or high-security TLS 1.2 ciphers. + +### Secure Redirects +* **HTTP to HTTPS:** Redirect all port 80 traffic to 443 at the server level (Nginx/Apache/Cloudflare) before the application logic. + +--- + +## 2. Browser Security Headers + +### Content Security Policy (CSP) +CSP is a primary defense against XSS and data injection. +* **Default Deny:** Start with `default-src 'none';`. +* **Script Safety:** Use `script-src 'self'` or nonces/hashes for inline scripts. Avoid `'unsafe-inline'` and `'unsafe-eval'`. +* **Connect Source:** Limit `connect-src` to trusted API endpoints. +* **Frame Control:** Use `frame-ancestors 'self'` to prevent Clickjacking. + +### Other Essential Headers +* **X-Content-Type-Options:** Prevent MIME-sniffing. + * *Syntax:* `X-Content-Type-Options: nosniff` +* **X-Frame-Options:** (Legacy) Prevent Clickjacking if CSP `frame-ancestors` isn't used. + * *Syntax:* `X-Frame-Options: DENY` or `SAMEORIGIN` +* **Referrer-Policy:** Control how much referrer information is sent. + * *Syntax:* `Referrer-Policy: strict-origin-when-cross-origin` +* **Permissions-Policy:** Restrict browser features like camera, microphone, or geolocation. + * *Syntax:* `Permissions-Policy: camera=(), microphone=(), geolocation=(self)` + +--- + +## 3. Injection Prevention + +### Cross-Site Scripting (XSS) +* **Data Binding:** Prefer `textContent` over `innerHTML` or `outerHTML`. +* **Context-Aware Encoding:** Encode data based on where it is placed (HTML body, attribute, JavaScript variable, CSS). +* **Trusted Types API (Modern):** Enforce policies that require strings to be sanitized before being passed to "sink" APIs like `innerHTML`. + * *Example:* `window.trustedTypes.createPolicy(...)` +* **Sanitizer API (Cutting Edge):** Use the native `Sanitizer` object for safe HTML parsing. + * *Example:* `const clean = new Sanitizer().sanitizeFor('div', dirtyHTML);` + +### SQL Injection +* **Parameterized Queries:** Never concatenate strings to build queries. Use placeholders. + * *Example (Node.js/pg):* `db.query('SELECT * FROM users WHERE id = $1', [userId])` +* **ORM Usage:** Use reputable ORMs (Sequelize, TypeORM, Prisma) but verify they use prepared statements for all operations. + +### OS Command Injection +* **Avoid Shell Execution:** Do not use `eval()`, `new Function()`, or `child_process.exec(untrustedInput)`. +* **Safe Alternatives:** Use `child_process.execFile` or `child_process.spawn` where arguments are passed as an array, bypassing the shell. + +--- + +## 4. Authentication & Session Management + +### Password Security +* **Hashing:** Never store passwords in plain text or using unsalted MD5/SHA1. Use **Argon2** (preferred), **bcrypt**, or **scrypt**. +* **Salting:** Use unique, cryptographically strong salts for every password. +* **Work Factor:** Adjust the cost factor/iterations to ensure hashing takes ~100-500ms to mitigate brute force. + +### Cookie Security +* **HttpOnly:** Prevent client-side scripts from accessing cookies. +* **Secure:** Ensure cookies are only sent over HTTPS. +* **SameSite:** Prevent CSRF by restricting cross-site cookie transmission. + * `SameSite=Lax`: Default, safe for most cases. + * `SameSite=Strict`: Most secure, only sent for first-party requests. +* **Prefixes:** Use `__Host-` or `__Secure-` prefixes for extra browser-level protection. + +### JSON Web Tokens (JWT) +* **Algorithm:** Prefer asymmetric signing (RS256, ES256) over symmetric (HS256). **Never** accept `alg: "none"`. +* **Validation:** Always validate `exp` (expiration), `iat` (issued at), and `iss` (issuer). +* **Storage:** Avoid storing sensitive JWTs in `localStorage`. Prefer HttpOnly, Secure cookies. + +### Modern Auth (Passkeys/WebAuthn) +* **WebAuthn API:** Support hardware-backed, phishing-resistant authentication using `navigator.credentials.create` and `navigator.credentials.get`. + +--- + +## 5. Cross-Site Request Forgery (CSRF) protection + +* **SameSite Cookies:** Use `SameSite=Lax` or `Strict` as the first line of defense. +* **Anti-CSRF Tokens:** For state-changing requests (POST, PUT, DELETE), use a unique, unpredictable token validated on the server. +* **Double Submit Cookie:** For stateless architectures, compare a value in a cookie with a value in the request header. +* **Custom Headers:** Requests with custom headers (e.g., `X-Requested-With`) trigger CORS preflight, providing implicit protection for APIs. + +--- + +## 6. Client-Side Cryptography + +### Web Crypto API (`window.crypto.subtle`) +* Use for high-performance, secure cryptographic operations in the browser. +* **Operations:** `encrypt`, `decrypt`, `sign`, `verify`, `digest`, `generateKey`. +* **Best Practice:** Always use cryptographically strong random values via `crypto.getRandomValues()`. + +--- + +## 7. Dependency & Supply Chain Security + +* **Auditing:** Run `npm audit` or `yarn audit` regularly. +* **Lockfiles:** Always commit `package-lock.json`, `yarn.lock`, or `pnpm-lock.yaml` to ensure deterministic builds. +* **Minimalism:** Minimize the number of dependencies. Audit small "helper" packages for maintenance and security history. +* **Automated Updates:** Use tools like Dependabot or Renovate to stay current with security patches. + +--- + +## 8. Data Privacy & Handling + +* **Encryption at Rest:** Encrypt sensitive data (PII, credentials) in the database. +* **Encryption in Transit:** Ensure all internal service communication uses TLS. +* **Logging:** Never log PII, passwords, or secrets. Implement log masking/filtering. +* **Error Messages:** Provide generic error messages to users. Detailed stack traces should only be available in internal logs. + +--- + +## 9. Cross-Origin Resource Sharing (CORS) + +* **Strict Origin Check:** Avoid `Access-Control-Allow-Origin: *`. Explicitly whitelist trusted origins. +* **Credential Control:** Only set `Access-Control-Allow-Credentials: true` if absolutely necessary and when the origin is not `*`. +* **Methods & Headers:** Explicitly whitelist permitted `Access-Control-Allow-Methods` and `Access-Control-Allow-Headers`. + +--- + +## 10. Modern Browser Isolation (Cutting Edge) + +* **Cross-Origin-Opener-Policy (COOP):** Isolate your window from cross-origin documents. + * *Syntax:* `Cross-Origin-Opener-Policy: same-origin` +* **Cross-Origin-Embedder-Policy (COEP):** Prevent loading cross-origin resources that don't explicitly allow it. + * *Syntax:* `Cross-Origin-Embedder-Policy: require-corp` +* **Subresource Integrity (SRI):** Use the `integrity` attribute on `<script>` and `<link>` tags to ensure external files haven't been tampered with. + * *Example:* `<script src="..." integrity="sha384-..." crossorigin="anonymous"></script>` diff --git a/.agents/skills/project-discipline-guides/mirrors/security/mirror.md b/.agents/skills/project-discipline-guides/mirrors/security/mirror.md new file mode 100644 index 000000000..af5817373 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/security/mirror.md @@ -0,0 +1,104 @@ +# Unified Lowest Common Denominator (LCD) Security Mirror + +This mirror represents the intersection of standard security practices consistently recognized across multiple technical baselines. It contains only the best practices, APIs, and guidelines explicitly present in all source documentation. + +--- + +### 1. Transport & Connection Security + +* **HTTPS Everywhere:** All application traffic must be served over HTTPS, including internal service communication. +* **HTTP to HTTPS Redirection:** Redirect all port 80 traffic to 443 at the server level. +* **Strict-Transport-Security (HSTS):** Use the HSTS header to enforce HTTPS. + * *Minimum Requirement:* `Strict-Transport-Security: max-age=31536000; includeSubDomains` +* **TLS Configuration:** Disable deprecated protocols (SSLv3, TLS 1.0, TLS 1.1). Support and prefer TLS 1.2 or TLS 1.3. + +--- + +### 2. Browser Security Headers + +* **Content Security Policy (CSP):** Implement CSP to mitigate XSS and injection. + * **Default Policy:** Use the `default-src` directive. + * **Script Safety:** Use nonces or hashes for scripts; avoid `'unsafe-inline'` and `'unsafe-eval'`. + * **Frame Control:** Use the `frame-ancestors` directive to prevent clickjacking. +* **X-Content-Type-Options:** Prevent MIME-sniffing. + * *Syntax:* `X-Content-Type-Options: nosniff` +* **Referrer-Policy:** Restrict referrer information sent to other origins. + * *Syntax:* `Referrer-Policy: strict-origin-when-cross-origin` +* **Permissions-Policy:** Restrict access to browser features. + * *Requirement:* Disable `camera`, `microphone`, and `geolocation` by default. +* **X-Frame-Options:** (Legacy) Use for older browser compatibility to prevent clickjacking. + * *Values:* `DENY` or `SAMEORIGIN` +* **Isolation Headers:** Implement cross-origin isolation. + * *COOP:* `Cross-Origin-Opener-Policy: same-origin` + * *COEP:* `Cross-Origin-Embedder-Policy: require-corp` + +--- + +### 3. Injection Prevention + +* **Cross-Site Scripting (XSS):** + * **Safe DOM APIs:** Prefer `textContent` over `innerHTML` or `outerHTML`. + * **Encoding:** Use context-aware encoding for data placed in HTML, attributes, or JavaScript. + * **Trusted Types API:** Use policies to govern string sinks like `innerHTML`. + * **Sanitizer API:** Use the native `Sanitizer` object for safe HTML parsing where available. +* **SQL Injection:** + * **Parameterized Queries:** Always use prepared statements or parameterized queries; never concatenate strings to build queries. + * **ORM Usage:** Ensure ORMs are configured to use prepared statements for all database operations. +* **Command Injection:** + * **Avoid Shell Invocation:** Do not use functions that execute strings in a shell (e.g., `child_process.exec`). + * **Safe Execution:** Use `spawn` or `execFile` where arguments are passed as an array, bypassing shell interpretation. + +--- + +### 4. Authentication & Session Management + +* **Password Security:** + * **Hashing:** Use slow, salted hashing algorithms: **Argon2**, **bcrypt**, or **scrypt**. + * **Salting:** Use unique, cryptographically strong salts for every password. + * **Work Factor:** Tune hashing parameters/iterations based on hardware to mitigate brute force. +* **Cookie Security:** + * **Attributes:** Always set `HttpOnly`, `Secure`, and `SameSite` (Lax or Strict). + * **Prefixes:** Use `__Host-` or `__Secure-` prefixes for session cookies. +* **JSON Web Tokens (JWT):** + * **Algorithm Safety:** Reject `alg: "none"` or unsigned tokens. Prefer asymmetric signing. + * **Validation:** Always validate `exp` (expiration), `iat` (issued at), and `iss` (issuer) claims. + * **Storage:** Avoid storing sensitive tokens in `localStorage`; prefer `HttpOnly` cookies. +* **Multi-Factor Authentication:** Support phishing-resistant authentication via the **WebAuthn** API / Passkeys. + +--- + +### 5. CSRF & CORS Protection + +* **CSRF Mitigation:** + * **SameSite:** Use `SameSite=Lax` or `Strict` as the primary defense. + * **Anti-CSRF Tokens:** Use unique, server-validated tokens for state-changing requests (POST, PUT, DELETE). + * **Custom Headers:** Use custom headers for AJAX requests to trigger CORS preflight. +* **CORS Configuration:** + * **Explicit Whitelisting:** Whitelist trusted origins explicitly; avoid `Access-Control-Allow-Origin: *`. + * **Credentials:** Never use `*` when `Access-Control-Allow-Credentials` is set to `true`. + +--- + +### 6. Client-Side Cryptography + +* **Web Crypto API:** Use `window.crypto.subtle` for cryptographic operations (encrypt, sign, digest). +* **Randomness:** Use `crypto.getRandomValues()` for all cryptographically strong random data. + +--- + +### 7. Dependency & Supply Chain Security + +* **Lockfiles:** Always commit lockfiles to ensure deterministic and reproducible builds. +* **Auditing:** Perform regular automated vulnerability scans (e.g., `npm audit`). +* **Minimalism:** Minimize the number of dependencies and audit small helper packages. +* **Subresource Integrity (SRI):** Use the `integrity` attribute when loading scripts or styles from CDNs. + +--- + +### 8. Data Privacy & Error Handling + +* **Encryption:** Implement encryption at rest for sensitive data and encryption in transit for all communications. +* **Logging:** Never log secrets, passwords, session IDs, or personally identifiable information (PII). +* **Error Handling:** + * **Generic Messages:** Provide generic error messages to end-users. + * **Stack Traces:** Ensure detailed stack traces and internal system details are never exposed in production responses. diff --git a/.agents/skills/project-discipline-guides/mirrors/webmcp/claude_mirror.md b/.agents/skills/project-discipline-guides/mirrors/webmcp/claude_mirror.md new file mode 100644 index 000000000..57ecf7c6c --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/webmcp/claude_mirror.md @@ -0,0 +1,493 @@ +# WebMCP Common Knowledge Guide + +A reference of default conventions, APIs, and best practices I would apply by default when writing WebMCP integrations in a browser context. This is intentionally exhaustive (no prioritization, no omissions for brevity). + +--- + +## 1. What WebMCP Is + +- WebMCP brings the Model Context Protocol (MCP) into the browser, letting a web page expose **tools**, **resources**, and **prompts** to an in-browser AI agent (or external client through a browser bridge). +- The page is the *server*; the user agent (browser/extension/embedded model) is the *client*. +- The core surface is `navigator.modelContext` (sometimes proposed under `window.ai.mcp` / `navigator.ai` in earlier drafts). Treat the entry point as feature-detected. +- WebMCP is a progressive enhancement: pages should function fully without it. + +--- + +## 2. Feature Detection & Initialization + +Always feature-detect before touching the API. Never assume presence. + +```js +if ('modelContext' in navigator) { + const mc = navigator.modelContext; + // register tools/resources/prompts +} +``` + +- Detect *capabilities*, not user agents. +- Wrap registration in a try/catch — APIs may exist but throw on unsupported options. +- Defer registration until DOM is ready when tools depend on DOM state: + +```js +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', register, { once: true }); +} else { + register(); +} +``` + +- Listen for the `modelcontextchange` (or equivalent) event if the runtime can be enabled/disabled mid-session. + +--- + +## 3. Tool Registration + +A **tool** is a callable function exposed to the agent. The standard registration shape: + +```js +navigator.modelContext.registerTool({ + name: 'add_to_cart', + description: 'Adds a product to the user\'s shopping cart.', + inputSchema: { + type: 'object', + properties: { + productId: { type: 'string', description: 'SKU of the product' }, + quantity: { type: 'integer', minimum: 1, default: 1 }, + }, + required: ['productId'], + additionalProperties: false, + }, + async execute({ productId, quantity }) { + const result = await cart.add(productId, quantity); + return { content: [{ type: 'text', text: `Added ${quantity}× ${productId}` }] }; + }, +}); +``` + +### Naming +- `snake_case` for tool names — matches MCP convention and is friendlier for LLM tool-call decoding. +- Verbs first: `search_products`, `open_dialog`, `submit_form`. +- Namespace when collisions are possible: `cart.add_item`, `nav.go_to`. +- Names should be stable; renaming a tool is a breaking change for any persisted agent context. + +### Descriptions +- Write descriptions for the *model*, not the developer. State purpose, side effects, and constraints in plain English. +- Mention preconditions (“user must be logged in”), idempotency, and what the agent should *not* use the tool for. +- Keep under ~1–2 sentences when possible; the description is part of every prompt. + +### Lifetime +- `registerTool` typically returns an unregister handle (function or `Disposable`). Hold it and clean up on SPA route change or component unmount. +- Re-register tools when the page state that backs them changes (e.g., after login, the `logout` tool becomes available). + +--- + +## 4. Input Schema (JSON Schema) Design + +Tools use **JSON Schema Draft 2020-12** for their input shape. This is the model’s contract. + +### Defaults +- Always set `type: 'object'` at the top level. +- Always set `additionalProperties: false` — prevents the model from passing junk and tightens validation. +- Always list `required` explicitly; don’t rely on truthiness. +- Use `description` on *every* property — the model reads them. +- Use `enum` for closed sets; use `format` for `date-time`, `email`, `uri`, etc. +- Prefer `integer` over `number` when fractional values are nonsensical. +- Use `minimum`/`maximum`/`minLength`/`maxLength`/`pattern` to constrain. The model will follow these. + +### Schema patterns + +```js +inputSchema: { + type: 'object', + additionalProperties: false, + required: ['query'], + properties: { + query: { type: 'string', minLength: 1, description: 'Search terms' }, + limit: { type: 'integer', minimum: 1, maximum: 50, default: 10 }, + sort: { type: 'string', enum: ['relevance', 'price_asc', 'price_desc'] }, + filters: { + type: 'object', + additionalProperties: false, + properties: { + inStock: { type: 'boolean' }, + priceRange: { + type: 'object', + additionalProperties: false, + properties: { + min: { type: 'number', minimum: 0 }, + max: { type: 'number', minimum: 0 }, + }, + }, + }, + }, + }, +} +``` + +### Anti-patterns +- Free-form `object` with no properties (`{ type: 'object' }` alone) — the model has no signal. +- `oneOf`/`anyOf` at the top level — split into separate tools instead. One tool, one job. +- Embedding entire data structures in the schema (use resources for that). +- Schemas that allow ambiguous combinations (e.g., both `userId` and `userEmail` optional with no `oneOf`). + +--- + +## 5. Tool Handlers + +```js +async execute(args, { signal, requestId } = {}) { + // ... +} +``` + +- Handlers should be `async` by default. Avoid blocking the main thread. +- Validate inputs even if the schema does — the runtime *may* not enforce strict mode. +- Honor the `AbortSignal` from the second argument: pass it to `fetch`, listen for `abort`, and throw `DOMException('Aborted', 'AbortError')` promptly. +- Never trust input — sanitize before injecting into the DOM, URLs, or storage. + +```js +async execute({ id }, { signal }) { + const res = await fetch(`/api/items/${encodeURIComponent(id)}`, { signal }); + if (!res.ok) throw new Error(`Failed (${res.status})`); + return { content: [{ type: 'text', text: await res.text() }] }; +} +``` + +### Side effects +- Tools that mutate state (POST/PUT/DELETE) should be marked appropriately if the API supports `annotations` (e.g., `{ destructiveHint: true, idempotentHint: false }`). MCP-aligned clients use these for confirmation UX. +- Read-only tools should set `readOnlyHint: true`. + +--- + +## 6. Returning Results + +The standard return shape is `{ content: ContentBlock[], isError?: boolean }`: + +```js +return { + content: [ + { type: 'text', text: 'Order #1234 created.' }, + ], +}; +``` + +Content block types you can use natively: +- `{ type: 'text', text: string }` — primary channel. +- `{ type: 'image', data: base64, mimeType: 'image/png' }` — for screenshots, charts. +- `{ type: 'resource', resource: { uri, mimeType, text? | blob? } }` — references a registered resource. +- `{ type: 'audio', data, mimeType }` — for voice tools. + +### Conventions +- Return the **smallest useful** payload. The model will quote/summarize it; large blobs cost tokens. +- Prefer structured `text` (JSON or short prose) over images when the agent only needs facts. +- For long results, return a *resource reference* and let the agent request it on demand. +- Set `isError: true` for *recoverable* errors so the model can react; `throw` for truly exceptional failures. + +```js +return { content: [{ type: 'text', text: 'Item not found' }], isError: true }; +``` + +--- + +## 7. Error Handling + +- Throw `Error` subclasses with informative messages — the runtime serializes these to the client. +- Surface validation failures as `isError: true` results, not exceptions, when the agent could retry with corrected input. +- Use `AbortError` for cancellations; do not log or report them as failures. +- Never leak stack traces with sensitive info to the model. +- Wrap `fetch` calls and check `response.ok` — `fetch` only rejects on network failure, not HTTP errors. + +```js +try { + const r = await fetch(url, { signal }); + if (!r.ok) { + return { + isError: true, + content: [{ type: 'text', text: `HTTP ${r.status}: ${r.statusText}` }], + }; + } +} catch (err) { + if (err.name === 'AbortError') throw err; + return { isError: true, content: [{ type: 'text', text: 'Network error' }] }; +} +``` + +--- + +## 8. Resources + +Resources expose **data** (not actions) the agent can read. + +```js +navigator.modelContext.registerResource({ + uri: 'app://cart/current', + name: 'Current shopping cart', + description: 'Live contents of the user\'s cart', + mimeType: 'application/json', + async read() { + return { contents: [{ uri: 'app://cart/current', mimeType: 'application/json', text: JSON.stringify(cart.toJSON()) }] }; + }, +}); +``` + +### Conventions +- URIs use a custom scheme (`app://`, `page://`, or your domain). Stable across reads. +- `mimeType` should be accurate — agents use it to decide how to render. +- Resources can be **subscribable**; emit `notifications/resources/updated` (or use the runtime helper) when the underlying data changes. Throttle updates. +- Prefer **resource templates** (URI templates per RFC 6570) for parameterized data: `app://orders/{orderId}`. + +--- + +## 9. Prompts + +Prompts are reusable templates the user (or agent) can invoke. + +```js +navigator.modelContext.registerPrompt({ + name: 'summarize_page', + description: 'Summarize the current page in 3 bullet points.', + arguments: [ + { name: 'tone', description: 'casual | formal', required: false }, + ], + async get({ tone = 'casual' }) { + return { + messages: [ + { role: 'user', content: { type: 'text', text: `Summarize in ${tone} tone:\n\n${document.body.innerText}` } }, + ], + }; + }, +}); +``` + +- Keep prompts small and parameter-driven; don’t hardcode user data. +- Prefer prompts over tools for *templated text generation* with no side effects. + +--- + +## 10. Lifecycle, Cleanup, SPAs + +- Register on mount, unregister on unmount. Memory leaks here mean stale tools that throw or operate on detached DOM. +- For frameworks: register inside `useEffect` (React), `onMount` (Svelte/Solid), `mounted` (Vue), and return the disposer. +- On SPA route changes, swap registrations — don’t leave product-detail tools alive on the checkout page. +- Use `AbortController` per page/component to cancel any in-flight tool calls when navigating away. + +```js +useEffect(() => { + const dispose = navigator.modelContext.registerTool({ /* ... */ }); + return () => dispose(); +}, [productId]); +``` + +--- + +## 11. Permissions & User Consent + +- Treat tool execution as a **user-initiated** action wherever it has side effects. The runtime will typically ask for confirmation, but the page should also be defensive. +- Never auto-execute privileged operations (purchases, deletions, sends) without an in-page confirmation step *in addition* to the runtime’s consent UI. +- Surface a clear, in-page indicator when the agent is acting on the user’s behalf (e.g., a banner, an aria-live region announcement). +- Respect `Permissions-Policy` and same-origin restrictions. + +--- + +## 12. Security + +- **Origin discipline**: only register tools that operate on data the origin owns. Don’t expose third-party iframes’ DOM through tools you control. +- **Input sanitization**: always escape into the DOM with `textContent`, not `innerHTML`. Use `DOMPurify` or the Trusted Types API when HTML is unavoidable. +- **URL construction**: use `new URL(input, base)` and validate `protocol` against an allowlist (`https:`, `http:`). +- **Avoid prompt injection**: when echoing user/server data into a tool description or prompt template, treat it as untrusted. Never let user-controlled strings define new tools. +- **CSRF**: tool handlers that POST should still respect your CSRF token strategy; the agent does not bypass it. +- **Secrets**: never expose API keys via tool inputs/outputs. The agent log will see them. +- **Rate limit** tool execution at the handler level — a runaway model can call a tool in a tight loop. + +--- + +## 13. Async Patterns + +- Always `await` and surface errors. Unhandled rejections silently break tool calls. +- Use `AbortController` and pass `signal` through to `fetch`, `addEventListener`, and any custom async APIs. +- Use `Promise.all` for parallel reads; never serialize independent fetches. +- Use `queueMicrotask` for scheduling DOM-coupled follow-ups; use `requestIdleCallback` for low-priority cleanup. +- Use `AbortSignal.timeout(ms)` for per-call deadlines — supported in all current major browsers. +- Use `AbortSignal.any([a, b])` to combine the runtime signal with your own timeout. + +```js +const signal = AbortSignal.any([opts.signal, AbortSignal.timeout(5000)]); +``` + +--- + +## 14. Progressive Enhancement & Fallbacks + +- The page must work without WebMCP. WebMCP is *additive*. +- Gate registration entirely behind feature detection — no top-level imports that throw. +- For cutting-edge features (subscribable resources, streaming results), check capability flags exposed by the runtime; fall back to one-shot reads. +- Polyfill MCP types via `@modelcontextprotocol/sdk` if you need the type defs in TypeScript. + +--- + +## 15. Testing + +- Unit-test handlers in isolation by calling `execute(args)` directly with mocked DOM / fetch. +- Use `MSW` (Mock Service Worker) to mock network calls in tool handlers. +- Mock `navigator.modelContext` in tests: + +```js +beforeEach(() => { + globalThis.navigator.modelContext = { + registerTool: vi.fn(() => () => {}), + registerResource: vi.fn(() => () => {}), + }; +}); +``` + +- Validate every tool’s `inputSchema` against `ajv` in a test — schema typos otherwise only surface at runtime. +- Snapshot the registered tool list per route to catch accidental tool leakage between pages. +- Run an end-to-end test where a real (or mocked) MCP client invokes each tool with synthetic inputs. + +--- + +## 16. Performance + +- Keep `inputSchema` and `description` payloads small; the entire registry is sent on every model turn. +- Don’t register hundreds of tools — the prompt budget can’t absorb it. Group related actions or use parameters. +- Memoize expensive setup outside the handler: + +```js +const indexPromise = buildSearchIndex(); +navigator.modelContext.registerTool({ + name: 'search', + async execute({ q }) { + const idx = await indexPromise; + return { content: [{ type: 'text', text: idx.search(q).join('\n') }] }; + }, +}); +``` + +- Stream long results when supported; otherwise paginate via tool arguments (`offset`, `limit`). +- Use `structuredClone` (built-in everywhere modern) instead of `JSON.parse(JSON.stringify(...))`. + +--- + +## 17. Accessibility & UX of Agent Actions + +- Reflect agent-driven changes via `aria-live="polite"` regions so screen readers announce them. +- Ensure focus management when the agent opens dialogs or navigates — set focus to the new context. +- Don’t hijack focus mid-typing if the user is interacting. +- Honor `prefers-reduced-motion` for any UI feedback the agent triggers. + +--- + +## 18. Code Organization + +- One file per tool (or per feature area) — `tools/cart.js`, `tools/search.js`. +- Central `registerAll()` that the app calls once after auth/state is ready. +- Co-locate the schema with the handler; never define them in separate files. +- Type schemas with `as const satisfies JSONSchema` (TypeScript) and derive the handler arg type via a `FromSchema` helper (`json-schema-to-ts` or `zod-to-json-schema`). +- For Zod-first projects: define a `z.object(...)`, derive both the JSON Schema (via `zod-to-json-schema`) and the TS type from it. + +```ts +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +const Input = z.object({ id: z.string().uuid() }); + +navigator.modelContext.registerTool({ + name: 'get_user', + description: '...', + inputSchema: zodToJsonSchema(Input), + async execute(raw) { + const { id } = Input.parse(raw); // runtime validation + // ... + }, +}); +``` + +--- + +## 19. TypeScript Conventions + +- Import types from `@modelcontextprotocol/sdk/types.js` when they exist; otherwise declare a minimal `Tool`, `Resource`, `Prompt` interface locally. +- Avoid `any` in handlers; derive arg types from the schema. +- Use `unknown` for the raw input and narrow with the validator (Zod / Ajv). +- Mark side-effect tools with a branded type if your codebase distinguishes read-only vs mutating. + +--- + +## 20. Common JavaScript / Web Platform Defaults Used Inside Tools + +These are baseline platform features I would use without comment: + +- `fetch` with `AbortSignal` (never `XMLHttpRequest`). +- `URL` and `URLSearchParams` for URL construction. +- `structuredClone` for deep copies. +- `Object.hasOwn(obj, key)` over `hasOwnProperty.call`. +- `Array.prototype.at(-1)` for last element. +- `Array.prototype.flat`, `flatMap`, `findLast`, `findLastIndex`, `group`/`groupBy` (where shipped). +- `Promise.allSettled`, `Promise.any`, `Promise.withResolvers`. +- `Intl.DateTimeFormat`, `Intl.NumberFormat`, `Intl.ListFormat`, `Intl.RelativeTimeFormat` for locale-aware output. +- `crypto.randomUUID()` for IDs (don’t hand-roll). +- `crypto.subtle` for hashing. +- Optional chaining (`?.`), nullish coalescing (`??`), logical assignment (`??=`, `&&=`, `||=`). +- Top-level `await` in modules. +- `import.meta` for module-relative URLs. +- ES modules (`type="module"`) over IIFEs/UMD. +- `const` by default, `let` only when reassigned, never `var`. +- `for...of` over `forEach` when `await` is needed inside the loop. +- `Map`/`Set` over object/array when keys aren’t static strings. +- `WeakRef` / `FinalizationRegistry` only when there’s a clear reason; otherwise let GC do its job. +- `EventTarget` subclasses for custom event emitters (no third-party `EventEmitter`). +- `customElements.define` for any reusable widget; Shadow DOM for style isolation. +- `dialog` element for modals; `popover` attribute for popovers (where shipped). +- `:has()`, `:is()`, `:where()` selectors in CSS-related tool output. +- View Transitions API (`document.startViewTransition`) when navigating in response to a tool call (Chromium-shipped, gracefully degrades). + +--- + +## 21. Clean Code Principles I Apply by Default + +- Small functions; one tool = one job. Decompose multi-step workflows into multiple tools the agent can compose. +- Pure functions where possible; isolate side effects in handlers. +- Early returns over nested conditionals. +- Prefer immutability; use spread/`structuredClone` rather than mutating shared state. +- Name things for the *reader*: `productId`, not `pid`; `cancelOrder`, not `co`. +- Avoid magic numbers; lift to named constants at module top. +- No comments restating code; only comments explaining *why*. +- No dead code, no commented-out blocks. +- Fail fast: validate at the boundary, trust internally. +- One source of truth for shared state (a store, not duplicated module-level vars). +- Co-locate tests with code (`tool.ts` / `tool.test.ts`). + +--- + +## 22. Versioning & Compatibility + +- Treat tool *names* and *required input fields* as a public API. Removing or renaming is a breaking change. +- Add new optional fields freely; never repurpose an existing field. +- If a tool’s semantics change materially, register a new tool name and deprecate the old one with an updated description (`'(Deprecated: use search_v2)'`). +- Pin `@modelcontextprotocol/sdk` to a known-good version; check the changelog before upgrading. + +--- + +## 23. Observability + +- Log tool invocations with `requestId`, name, arg hash (not full args — PII), duration, and outcome. +- Use `performance.mark` / `performance.measure` around handlers for client-side timing. +- Send anonymized metrics (count, p50/p95 duration, error rate) per tool for capacity planning. +- Never log raw arguments containing user content without redaction. + +--- + +## 24. Things Not To Do (Defaults to Avoid) + +- Don’t register tools at module top-level outside a feature check — it crashes on unsupported browsers. +- Don’t expose tools that bypass auth or rate limits the rest of the app enforces. +- Don’t use `eval` / `new Function` on tool inputs — ever. +- Don’t make tools that *return* HTML strings; return text or structured data and let the page render. +- Don’t conflate tools and resources: actions are tools, data is resources. +- Don’t build tools that wrap *other* tools (no “meta tools”) — let the agent compose. +- Don’t depend on the agent reading a previous tool’s output verbatim; design each tool’s output to stand alone. +- Don’t mutate `navigator.modelContext` itself or monkey-patch the API surface. + +--- + +This is the baseline I would write to without explicit project guidance. Anything in a project guide that restates these defaults is redundant; anything that *contradicts* them is the value-add the project guide should keep. diff --git a/.agents/skills/project-discipline-guides/mirrors/webmcp/codex_mirror.md b/.agents/skills/project-discipline-guides/mirrors/webmcp/codex_mirror.md new file mode 100644 index 000000000..671b8d595 --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/webmcp/codex_mirror.md @@ -0,0 +1,3188 @@ +Below is my “Redundancy Mirror” for modern web development common knowledge: things I would generally know and apply without needing a project-specific guide. I’m answering from inherent knowledge only, without inspecting files or searching. + +**Scope** + +This covers browser-native web development: HTML, CSS, JavaScript, TypeScript-adjacent practices, DOM APIs, networking, storage, accessibility, performance, security, forms, progressive enhancement, Web Components, build hygiene, and clean code principles. + +It intentionally includes broad, standard guidance so project-specific docs can prune anything that is merely common practice. + +--- + +## Core Philosophy + +Modern web development should default to platform features before custom abstractions. + +Prefer: + +- Semantic HTML over generic `div` trees. +- CSS layout primitives over JavaScript measurement. +- Native form behavior over custom state machines. +- Browser APIs over unnecessary dependencies. +- Progressive enhancement over brittle browser detection. +- Small, composable modules over large framework-specific utilities. +- Clear state ownership over implicit side effects. +- Accessibility built into the structure, not patched on later. +- Feature detection over user-agent sniffing. +- Standards-based APIs over legacy libraries. + +A good default web implementation should still work when JavaScript is slow, delayed, partially failed, or unavailable, unless the product is inherently JavaScript-only. + +--- + +# JavaScript Language + +## Modern Syntax Defaults + +Use modern JavaScript syntax that is widely supported in current evergreen browsers. + +Default to: + +```js +const value = computeValue(); +let count = 0; +``` + +Use `const` unless reassignment is needed. Use `let` for reassignment. Avoid `var`. + +Use arrow functions for short callbacks: + +```js +items.map((item) => item.name); +``` + +Use named functions when stack traces, recursion, hoisting, or readability benefit: + +```js +function parseUserInput(input) { + return input.trim(); +} +``` + +Use template literals for interpolation: + +```js +const label = `${user.name} (${user.role})`; +``` + +Use object shorthand: + +```js +const user = { id, name, email }; +``` + +Use destructuring when it improves clarity: + +```js +const { id, name } = user; +const [first, second] = items; +``` + +Do not overuse destructuring when it obscures the source object or creates long function signatures. + +Use default parameters: + +```js +function createButton(label = 'Submit') { + return label; +} +``` + +Use rest parameters instead of `arguments`: + +```js +function sum(...numbers) { + return numbers.reduce((total, n) => total + n, 0); +} +``` + +Use spread syntax for shallow copies and argument expansion: + +```js +const nextItems = [...items, newItem]; +const nextUser = { ...user, name: 'Ada' }; +``` + +Remember spread is shallow. Nested objects still share references. + +Use optional chaining for nullable paths: + +```js +const city = user.address?.city; +``` + +Use nullish coalescing for fallback only on `null` or `undefined`: + +```js +const pageSize = options.pageSize ?? 20; +``` + +Avoid `||` defaults when `0`, `false`, or `''` are valid values. + +Use logical assignment when it is clear: + +```js +config.timeout ??= 5000; +cache[key] ||= computeValue(); +``` + +Avoid clever chaining when simple conditionals are easier to read. + +Use numeric separators for long literals: + +```js +const maxBytes = 10_000_000; +``` + +Use `for...of` for iterable values when you need sequential control flow: + +```js +for (const item of items) { + process(item); +} +``` + +Use array methods for transformations: + +```js +const activeUsers = users.filter((user) => user.active); +const names = users.map((user) => user.name); +``` + +Avoid using `map` for side effects. + +Use `Object.hasOwn()` instead of `obj.hasOwnProperty()`: + +```js +if (Object.hasOwn(record, key)) { + // ... +} +``` + +Use `Array.prototype.at()` for relative indexing: + +```js +const last = items.at(-1); +``` + +Use `structuredClone()` for deep cloning structured data when supported and appropriate: + +```js +const copy = structuredClone(data); +``` + +Do not use `JSON.parse(JSON.stringify(value))` as a general-purpose deep clone. It loses dates, maps, sets, `undefined`, special numbers, prototypes, and non-JSON values. + +Use `Promise.all()` for independent concurrent work: + +```js +const [user, settings] = await Promise.all([ + fetchUser(id), + fetchSettings(id), +]); +``` + +Use `Promise.allSettled()` when partial failures are expected: + +```js +const results = await Promise.allSettled(tasks); +``` + +Use `Promise.race()` or `AbortController` for timeout/cancellation patterns. + +Use top-level `await` only in modules where startup ordering is intentional. + +Use `try`/`catch` around awaited operations that can fail and need local recovery. + +```js +try { + const response = await fetch(url); +} catch (error) { + showNetworkError(error); +} +``` + +Do not catch errors only to log and rethrow unless adding useful context. + +Use `Error` with meaningful messages: + +```js +throw new Error(`Expected user ${id} to exist`); +``` + +Use `cause` when wrapping errors: + +```js +throw new Error('Failed to load profile', { cause: error }); +``` + +Use classes when modeling stateful entities with behavior: + +```js +class Store { + #items = []; + + add(item) { + this.#items.push(item); + } + + get items() { + return [...this.#items]; + } +} +``` + +Use private class fields for encapsulation when appropriate: + +```js +class Counter { + #value = 0; + + increment() { + this.#value += 1; + } +} +``` + +Prefer plain objects and functions for simple data transformations. + +--- + +# JavaScript Data Structures + +Use arrays for ordered lists. + +Use objects for records with known string keys. + +Use `Map` for dynamic key-value collections, especially when keys are not strings or insertion order matters: + +```js +const byId = new Map(); +byId.set(user.id, user); +``` + +Use `Set` for uniqueness: + +```js +const selectedIds = new Set(); +selectedIds.add(id); +``` + +Use `WeakMap` for metadata associated with object lifetimes: + +```js +const metadata = new WeakMap(); +metadata.set(element, { initialized: true }); +``` + +Use `Date` carefully. Native `Date` is mutable and has time zone pitfalls. + +For dates: + +- Store machine timestamps as ISO 8601 strings or epoch milliseconds. +- Use UTC for persistence when possible. +- Use `Intl.DateTimeFormat` for display. +- Avoid hand-rolled date formatting. +- Be explicit about local time versus absolute time. + +Use `URL` and `URLSearchParams` instead of string concatenation: + +```js +const url = new URL('/search', location.origin); +url.searchParams.set('q', query); +url.searchParams.set('page', String(page)); +``` + +Use `Intl` APIs for locale-aware formatting: + +```js +const currency = new Intl.NumberFormat(locale, { + style: 'currency', + currency: 'USD', +}).format(amount); + +const date = new Intl.DateTimeFormat(locale, { + dateStyle: 'medium', + timeStyle: 'short', +}).format(new Date()); +``` + +Use `Intl.Collator` for human sorting: + +```js +const collator = new Intl.Collator(locale, { sensitivity: 'base' }); +items.sort((a, b) => collator.compare(a.name, b.name)); +``` + +--- + +# Modules + +Use ES modules. + +```js +export function formatName(user) { + return `${user.firstName} ${user.lastName}`; +} + +import { formatName } from './format-name.js'; +``` + +Prefer named exports for shared utilities because they refactor and tree-shake well. + +Use default exports when a module clearly has one primary export, such as a component or class. + +Keep modules cohesive. A module should have a clear reason to change. + +Avoid circular dependencies. If two modules depend on each other, extract shared behavior into a third module. + +Avoid side effects in utility modules. Side effects should be explicit at application boundaries. + +Prefer explicit imports over global variables. + +Use dynamic `import()` for lazy loading: + +```js +const { openDialog } = await import('./dialog.js'); +openDialog(); +``` + +Use import maps where appropriate in no-build or platform-oriented projects. + +--- + +# TypeScript-Adjacent Common Knowledge + +Even in JavaScript projects, code benefits from type-shaped thinking. + +Prefer precise data contracts. + +Use JSDoc when not using TypeScript: + +```js +/** + * @param {{ id: string, name: string }} user + */ +function renderUser(user) { + // ... +} +``` + +If using TypeScript: + +- Prefer `unknown` over `any`. +- Narrow external input before use. +- Use discriminated unions for state machines. +- Prefer `type` for unions and object aliases. +- Prefer `interface` when declaration merging or object shape extension is useful. +- Avoid non-null assertions unless the invariant is obvious and enforced. +- Keep public types stable and internal types flexible. +- Do not model everything with classes. +- Validate runtime input even if TypeScript says it is typed. + +Example discriminated union: + +```ts +type LoadState<T> = + | { status: 'idle' } + | { status: 'loading' } + | { status: 'success'; data: T } + | { status: 'error'; error: Error }; +``` + +External data must be treated as untrusted: + +```ts +const data: unknown = await response.json(); +``` + +Use runtime schemas or explicit validation at boundaries. + +--- + +# DOM APIs + +Prefer scoped DOM queries: + +```js +const form = document.querySelector('form'); +const button = form?.querySelector('button[type="submit"]'); +``` + +Use `querySelector` and `querySelectorAll` for selector-based queries. + +Remember `querySelectorAll()` returns a static `NodeList`. + +Convert to array when needed: + +```js +const buttons = [...document.querySelectorAll('button')]; +``` + +Use `closest()` for event delegation and ancestor matching: + +```js +const button = event.target.closest('button[data-action]'); +``` + +Use `matches()` to test selectors: + +```js +if (element.matches('[aria-expanded="true"]')) { + // ... +} +``` + +Use `classList` instead of string manipulation: + +```js +element.classList.toggle('is-active', isActive); +``` + +Use `dataset` for simple custom data attributes: + +```html +<button data-user-id="123">Delete</button> +``` + +```js +const id = button.dataset.userId; +``` + +Use `textContent` for text insertion: + +```js +element.textContent = user.name; +``` + +Avoid `innerHTML` with untrusted content. If HTML insertion is required, sanitize it with a trusted sanitizer. + +Use `insertAdjacentHTML()` only with trusted or sanitized HTML. + +Prefer creating DOM nodes for dynamic content: + +```js +const item = document.createElement('li'); +item.textContent = label; +list.append(item); +``` + +Use `replaceChildren()` to replace content safely: + +```js +list.replaceChildren(...items.map(renderItem)); +``` + +Use `DocumentFragment` for batch insertion when useful: + +```js +const fragment = document.createDocumentFragment(); +for (const item of items) { + fragment.append(renderItem(item)); +} +list.append(fragment); +``` + +Use `template` elements for reusable inert markup: + +```html +<template id="item-template"> + <li><span></span></li> +</template> +``` + +Use `HTMLTemplateElement.content.cloneNode(true)` to instantiate. + +Use `Element.toggleAttribute()` for boolean attributes: + +```js +button.toggleAttribute('disabled', isSaving); +``` + +Set DOM properties when they represent live state: + +```js +input.value = value; +input.checked = checked; +button.disabled = isDisabled; +``` + +Set attributes when controlling markup semantics: + +```js +button.setAttribute('aria-expanded', String(expanded)); +``` + +--- + +# Events + +Use `addEventListener()`. + +```js +button.addEventListener('click', handleClick); +``` + +Prefer named handlers when removal or readability matters. + +Use event delegation for repeated dynamic elements: + +```js +list.addEventListener('click', (event) => { + const button = event.target.closest('button[data-id]'); + if (!button || !list.contains(button)) return; + + deleteItem(button.dataset.id); +}); +``` + +Use options: + +```js +element.addEventListener('click', handler, { once: true }); +window.addEventListener('scroll', onScroll, { passive: true }); +``` + +Use `{ passive: true }` for scroll/touch listeners that do not call `preventDefault()`. + +Use `AbortController` to clean up listeners: + +```js +const controller = new AbortController(); + +button.addEventListener('click', onClick, { + signal: controller.signal, +}); + +controller.abort(); +``` + +Use custom events for decoupled component communication: + +```js +element.dispatchEvent( + new CustomEvent('itemselect', { + bubbles: true, + detail: { id }, + }), +); +``` + +Use `preventDefault()` only when intentionally replacing native behavior. + +Use `stopPropagation()` sparingly. It can break composition. + +Do not rely on inline event attributes like `onclick`. + +--- + +# Fetch And Networking + +Use `fetch()` for HTTP requests. + +```js +const response = await fetch('/api/users'); + +if (!response.ok) { + throw new Error(`Request failed: ${response.status}`); +} + +const users = await response.json(); +``` + +Remember `fetch()` only rejects on network-level failures, not HTTP error statuses. + +Use `AbortController` for cancellation: + +```js +const controller = new AbortController(); + +const response = await fetch(url, { + signal: controller.signal, +}); + +controller.abort(); +``` + +Use timeouts explicitly: + +```js +function timeoutSignal(ms) { + const controller = new AbortController(); + setTimeout(() => controller.abort(), ms); + return controller.signal; +} +``` + +Use `URLSearchParams` for query strings: + +```js +const params = new URLSearchParams({ q: query, page: String(page) }); +const response = await fetch(`/search?${params}`); +``` + +Set headers intentionally: + +```js +await fetch('/api/items', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), +}); +``` + +Do not manually set `Content-Type` for `FormData`; the browser sets the multipart boundary. + +```js +const formData = new FormData(form); + +await fetch('/upload', { + method: 'POST', + body: formData, +}); +``` + +Use credentials deliberately: + +```js +fetch('/api/session', { + credentials: 'include', +}); +``` + +Be aware of CORS, cookies, SameSite, CSRF, and credentialed requests. + +Use `Response` helpers: + +```js +await response.text(); +await response.json(); +await response.blob(); +await response.arrayBuffer(); +``` + +Use streaming APIs for large responses when appropriate. + +Use `navigator.sendBeacon()` for low-priority analytics during page unload: + +```js +navigator.sendBeacon('/analytics', JSON.stringify(event)); +``` + +Use `keepalive: true` on small `fetch()` requests when appropriate during unload, with size constraints. + +--- + +# HTML + +Use semantic HTML first. + +Prefer: + +```html +<header> + <nav aria-label="Main"> + <a href="/">Home</a> + </nav> +</header> + +<main> + <h1>Account settings</h1> +</main> + +<footer></footer> +``` + +Avoid building everything from `div` and `span`. + +Use headings in a meaningful hierarchy. + +Use one primary `h1` for the page or application view. + +Use native controls: + +- `<button>` for actions. +- `<a href>` for navigation. +- `<input>` for input. +- `<select>` for selection. +- `<textarea>` for multiline text. +- `<details>` / `<summary>` for disclosure when suitable. +- `<dialog>` for modal and non-modal dialogs when appropriate. +- `<form>` for submissions. + +Do not use clickable `div`s when a button or link is correct. + +Buttons should have `type` inside forms: + +```html +<button type="submit">Save</button> +<button type="button">Cancel</button> +``` + +Use labels for form controls: + +```html +<label for="email">Email</label> +<input id="email" name="email" type="email" autocomplete="email"> +``` + +Or wrap the control: + +```html +<label> + Email + <input name="email" type="email"> +</label> +``` + +Use appropriate input types: + +```html +<input type="email"> +<input type="url"> +<input type="tel"> +<input type="number"> +<input type="date"> +<input type="search"> +<input type="password"> +``` + +Use autocomplete tokens: + +```html +<input autocomplete="given-name"> +<input autocomplete="family-name"> +<input autocomplete="email"> +<input autocomplete="current-password"> +<input autocomplete="new-password"> +``` + +Use built-in validation attributes: + +```html +<input required minlength="2" maxlength="64"> +<input type="email" required> +<input pattern="[0-9]{5}"> +``` + +Use `fieldset` and `legend` for grouped controls: + +```html +<fieldset> + <legend>Notification preferences</legend> + <label><input type="checkbox" name="email"> Email</label> +</fieldset> +``` + +Use `output` for calculated results: + +```html +<output name="total" for="quantity price"></output> +``` + +Use `time` for dates/times: + +```html +<time datetime="2026-05-13">May 13, 2026</time> +``` + +Use `picture` for art direction: + +```html +<picture> + <source media="(min-width: 800px)" srcset="large.webp"> + <img src="small.webp" alt="Description"> +</picture> +``` + +Use responsive images: + +```html +<img + src="image-800.jpg" + srcset="image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w" + sizes="(min-width: 800px) 50vw, 100vw" + alt="Description" +> +``` + +Use `loading="lazy"` for below-the-fold images: + +```html +<img src="photo.jpg" alt="..." loading="lazy"> +``` + +Use `decoding="async"` for non-critical images: + +```html +<img src="photo.jpg" alt="..." decoding="async"> +``` + +Set explicit dimensions or aspect ratio for media to reduce layout shift: + +```html +<img src="photo.jpg" width="800" height="600" alt="..."> +``` + +Use `iframe` titles: + +```html +<iframe title="Map of store location" src="..."></iframe> +``` + +Use `sandbox` on iframes when embedding untrusted content: + +```html +<iframe sandbox="allow-scripts allow-same-origin" src="..."></iframe> +``` + +Use `meta viewport`: + +```html +<meta name="viewport" content="width=device-width, initial-scale=1"> +``` + +Use `charset`: + +```html +<meta charset="utf-8"> +``` + +Use `lang`: + +```html +<html lang="en"> +``` + +--- + +# Accessibility + +Accessibility is standard web quality, not an optional layer. + +Start with semantic HTML. Native elements provide keyboard support, roles, names, states, and platform behavior. + +Use ARIA only when native HTML cannot express the needed semantics. + +First rule of ARIA: prefer no ARIA when semantic HTML works. + +Use accessible names for controls: + +```html +<button aria-label="Close"> + <svg aria-hidden="true">...</svg> +</button> +``` + +Do not use icon-only buttons without labels. + +Use `aria-hidden="true"` for decorative icons. + +Do not put focusable elements inside `aria-hidden` containers. + +Use `alt` text for meaningful images: + +```html +<img src="chart.png" alt="Sales increased from January to March"> +``` + +Use empty alt for decorative images: + +```html +<img src="divider.png" alt=""> +``` + +Do not include “image of” or “picture of” unless context requires it. + +Use visible focus states. Do not remove outlines without replacement. + +Prefer `:focus-visible`: + +```css +button:focus-visible { + outline: 2px solid CanvasText; + outline-offset: 2px; +} +``` + +Ensure keyboard access: + +- Tab reaches interactive controls. +- Shift+Tab moves backward. +- Enter/Space activate buttons. +- Escape closes modals/popovers where expected. +- Focus is managed after opening/closing dialogs. +- No keyboard traps. + +Use `dialog.showModal()` for modal dialogs when appropriate: + +```js +dialog.showModal(); +dialog.close(); +``` + +When building custom dialogs, manage: + +- Initial focus. +- Focus containment. +- Escape behavior. +- Return focus to opener. +- Background inertness. +- Accessible name. + +Use `inert` to make inactive regions unfocusable when supported: + +```html +<main inert></main> +``` + +Use live regions for dynamic status updates: + +```html +<div role="status" aria-live="polite"></div> +``` + +Use `aria-live="assertive"` sparingly. + +Use `aria-expanded` for disclosure triggers: + +```html +<button aria-expanded="false" aria-controls="menu">Menu</button> +``` + +Use `aria-current` for current navigation item: + +```html +<a href="/settings" aria-current="page">Settings</a> +``` + +Use `aria-describedby` for help and error text: + +```html +<input id="email" aria-describedby="email-error"> +<p id="email-error">Enter a valid email address.</p> +``` + +Do not use placeholder text as the only label. + +Ensure color contrast: + +- Normal text should meet WCAG contrast expectations. +- Important graphical indicators should not rely only on color. +- Focus indicators must be visible. + +Respect user preferences: + +```css +@media (prefers-reduced-motion: reduce) { + * { + scroll-behavior: auto; + } +} +``` + +Do not disable zoom. + +Use logical reading order. Visual order should not contradict DOM order. + +Use skip links for complex pages: + +```html +<a class="skip-link" href="#main">Skip to content</a> +``` + +--- + +# CSS Modern Defaults + +Use modern layout primitives. + +Prefer Flexbox for one-dimensional layout: + +```css +.toolbar { + display: flex; + align-items: center; + gap: 0.5rem; +} +``` + +Prefer Grid for two-dimensional layout: + +```css +.layout { + display: grid; + grid-template-columns: 16rem 1fr; + gap: 1rem; +} +``` + +Use `gap` instead of margins between flex/grid children where possible. + +Use logical properties for internationalization: + +```css +.card { + padding-block: 1rem; + padding-inline: 1.25rem; + margin-block-end: 1rem; +} +``` + +Use `inline-size` / `block-size` where appropriate instead of `width` / `height`. + +Use modern viewport units carefully: + +```css +.hero { + min-block-size: 100dvh; +} +``` + +Prefer `dvh`, `svh`, or `lvh` when mobile browser UI behavior matters. + +Use `min()`, `max()`, and `clamp()`: + +```css +.container { + inline-size: min(100% - 2rem, 72rem); + margin-inline: auto; +} + +.title { + font-size: clamp(2rem, 4vw, 4rem); +} +``` + +Avoid viewport-based font scaling when it harms readability or causes layout instability. Use restrained `clamp()` if needed. + +Use custom properties for design tokens and theming: + +```css +:root { + --color-bg: #fff; + --color-text: #111; + --space-2: 0.5rem; +} + +.card { + background: var(--color-bg); + color: var(--color-text); + padding: var(--space-2); +} +``` + +Use cascade layers to manage CSS ordering when appropriate: + +```css +@layer reset, base, components, utilities; + +@layer base { + body { + margin: 0; + } +} +``` + +Use `@supports` for progressive enhancement: + +```css +@supports (container-type: inline-size) { + .card-grid { + container-type: inline-size; + } +} +``` + +Use container queries for component-responsive layouts: + +```css +.card-list { + container-type: inline-size; +} + +@container (min-width: 40rem) { + .card { + grid-template-columns: auto 1fr; + } +} +``` + +Use style queries only as progressive enhancement where supported. + +Use `:is()` to simplify selector lists: + +```css +:is(h1, h2, h3) { + line-height: 1.1; +} +``` + +Use `:where()` for zero-specificity defaults: + +```css +:where(ul, ol) { + padding-inline-start: 1.5rem; +} +``` + +Use `:has()` for parent-aware styling where supported: + +```css +.field:has(input:invalid) { + border-color: red; +} +``` + +Use `:focus-visible` for keyboard focus styling. + +Use `accent-color` for native controls: + +```css +input[type='checkbox'] { + accent-color: rebeccapurple; +} +``` + +Use `color-scheme` for native dark/light integration: + +```css +:root { + color-scheme: light dark; +} +``` + +Use `light-dark()` as progressive enhancement where available: + +```css +:root { + color: light-dark(#111, #eee); + background: light-dark(#fff, #111); +} +``` + +Use `@media (prefers-color-scheme: dark)` for dark mode: + +```css +@media (prefers-color-scheme: dark) { + :root { + --color-bg: #111; + --color-text: #eee; + } +} +``` + +Use `@media (prefers-reduced-motion: reduce)` to reduce motion. + +Use `@media (forced-colors: active)` to support high-contrast modes: + +```css +@media (forced-colors: active) { + button { + border: 1px solid ButtonText; + } +} +``` + +Use `scroll-margin` for anchored content under sticky headers: + +```css +section { + scroll-margin-block-start: 5rem; +} +``` + +Use `overscroll-behavior` where scroll chaining should be controlled: + +```css +.modal { + overscroll-behavior: contain; +} +``` + +Use `aspect-ratio` for stable media boxes: + +```css +.video { + aspect-ratio: 16 / 9; +} +``` + +Use `object-fit` for replaced elements: + +```css +img { + object-fit: cover; +} +``` + +Use `text-wrap: balance` for headings as progressive enhancement: + +```css +h1 { + text-wrap: balance; +} +``` + +Use `overflow-wrap: anywhere` for untrusted long text: + +```css +.card { + overflow-wrap: anywhere; +} +``` + +Use `line-clamp` carefully as progressive enhancement: + +```css +.summary { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} +``` + +Use CSS nesting if supported by the target browsers/build pipeline: + +```css +.card { + padding: 1rem; + + & h2 { + margin-block-start: 0; + } +} +``` + +Avoid excessive selector specificity. + +Avoid styling by brittle DOM depth: + +```css +/* Brittle */ +.sidebar > div > ul > li > a {} +``` + +Prefer class-based component selectors. + +Use `box-sizing: border-box` globally: + +```css +*, +*::before, +*::after { + box-sizing: border-box; +} +``` + +Use a minimal reset instead of aggressive resets that remove useful native behavior. + +Avoid `!important` except for deliberate utility overrides or external integration boundaries. + +Avoid layout shifts by reserving space for dynamic content. + +Avoid animating layout properties like `width`, `height`, `top`, `left` when transform/opacity can work. + +Animate: + +```css +transform +opacity +filter +``` + +Be careful with `will-change`; use it sparingly and remove it when no longer needed. + +--- + +# Forms + +Use native forms whenever data submission or validation is involved. + +```html +<form method="post" action="/account"> + <label for="name">Name</label> + <input id="name" name="name" required> + <button type="submit">Save</button> +</form> +``` + +Use `FormData` to collect form values: + +```js +const formData = new FormData(form); +const name = formData.get('name'); +``` + +Use `requestSubmit()` instead of programmatically clicking submit buttons: + +```js +form.requestSubmit(); +``` + +Use constraint validation APIs: + +```js +if (!form.reportValidity()) { + return; +} +``` + +Set custom validation messages carefully: + +```js +input.setCustomValidity('Choose a username.'); +input.reportValidity(); +input.setCustomValidity(''); +``` + +Use `inputmode` for mobile keyboard hints: + +```html +<input inputmode="numeric"> +<input inputmode="decimal"> +<input inputmode="email"> +``` + +Use `autocomplete` for better UX. + +Use `name` attributes for successful controls. + +Do not rely only on client-side validation. Server validation is required. + +Avoid blocking paste in fields. + +Avoid hostile password rules and arbitrary restrictions. + +Use accessible error messages connected to controls. + +Preserve user input on validation errors. + +Disable submit buttons only when necessary, and ensure users receive feedback. + +--- + +# Web Components + +Use Web Components when native encapsulation, framework independence, or reusable browser-level components are valuable. + +Define custom elements with hyphenated names: + +```js +customElements.define('user-card', UserCard); +``` + +Use `HTMLElement` subclasses: + +```js +class UserCard extends HTMLElement { + connectedCallback() { + this.textContent = 'User'; + } +} +``` + +Use lifecycle callbacks: + +```js +class MyElement extends HTMLElement { + connectedCallback() {} + disconnectedCallback() {} + attributeChangedCallback(name, oldValue, newValue) {} + + static get observedAttributes() { + return ['open']; + } +} +``` + +Use Shadow DOM for style/DOM encapsulation: + +```js +class MyElement extends HTMLElement { + constructor() { + super(); + + const root = this.attachShadow({ mode: 'open' }); + root.innerHTML = `<slot></slot>`; + } +} +``` + +Prefer templates for larger component markup. + +Use slots for composition: + +```html +<my-card> + <h2 slot="title">Title</h2> + <p>Body</p> +</my-card> +``` + +Expose styling hooks through CSS custom properties and `part`: + +```html +<button part="button">Save</button> +``` + +```css +my-element::part(button) { + font-weight: bold; +} +``` + +Reflect simple boolean/string state to attributes when it affects styling, accessibility, or declarative usage. + +Avoid putting complex objects in attributes. Use properties for object values. + +Clean up timers, observers, and listeners in `disconnectedCallback`. + +Do not overuse Shadow DOM when global styling, accessibility relationships, or simple markup composition would be easier without it. + +Be aware that form-associated custom elements exist but require careful implementation. + +--- + +# Browser Storage + +Use `localStorage` only for small, non-sensitive, synchronous key-value data. + +```js +localStorage.setItem('theme', 'dark'); +const theme = localStorage.getItem('theme'); +``` + +Avoid storing secrets in `localStorage`. + +Remember `localStorage` is synchronous and can block. + +Use `sessionStorage` for tab-scoped data. + +Use IndexedDB for larger structured client-side storage. + +Use Cache Storage through service workers for offline resources and request/response caching. + +Use cookies for server-readable session state, with secure attributes: + +```http +Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax +``` + +Use `HttpOnly` cookies for sensitive session tokens so JavaScript cannot read them. + +Use `Secure` in production. + +Use `SameSite=Lax` or `Strict` unless cross-site usage is required. + +Do not store sensitive data in client-accessible storage. + +Version stored schemas. Handle migrations and corrupted data gracefully. + +--- + +# Service Workers And PWAs + +Use service workers for offline support, caching strategies, background sync-like behavior where available, and app shell caching. + +Register carefully: + +```js +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/service-worker.js'); +} +``` + +Use HTTPS, except localhost. + +Understand lifecycle: + +- install +- activate +- fetch +- update +- waiting worker +- clients claiming +- cache cleanup + +Use cache versioning: + +```js +const CACHE_NAME = 'app-v1'; +``` + +Clean old caches during activation. + +Use appropriate caching strategies: + +- Cache first for immutable static assets. +- Network first for frequently changing content. +- Stale-while-revalidate for content where fast cached responses are acceptable. +- Network only for sensitive or non-cacheable requests. + +Do not cache authenticated private data unless the product explicitly requires it and handles security implications. + +Handle offline fallbacks deliberately. + +Be careful with service worker bugs because they persist across reloads. + +Provide update UX when users need the newest code. + +Use the Web App Manifest for installable apps: + +```html +<link rel="manifest" href="/manifest.webmanifest"> +``` + +Include icons, name, short_name, start_url, display, background_color, and theme_color as needed. + +--- + +# Security + +Treat all external input as untrusted. + +Avoid XSS: + +- Use `textContent` for text. +- Avoid `innerHTML` with untrusted input. +- Sanitize user-generated HTML with a robust sanitizer. +- Use strict Content Security Policy where feasible. +- Avoid inline scripts. +- Escape data in the correct context. + +Use CSP: + +```http +Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self' +``` + +Use nonces or hashes if inline scripts are unavoidable. + +Avoid `eval`, `new Function`, string-based `setTimeout`, and string-based `setInterval`. + +Use Trusted Types where appropriate for larger apps: + +```http +Content-Security-Policy: require-trusted-types-for 'script' +``` + +Avoid DOM clobbering assumptions. Do not rely on global IDs becoming global variables. + +Protect against CSRF when using cookies for authentication: + +- SameSite cookies. +- CSRF tokens for unsafe methods when needed. +- Validate Origin/Referer where appropriate. + +Use HTTPS. + +Use secure cookie attributes. + +Do not expose secrets in frontend code. + +Do not trust hidden inputs or client-side checks. + +Validate and authorize on the server. + +Use Subresource Integrity for third-party static scripts when applicable: + +```html +<script + src="https://cdn.example.com/lib.js" + integrity="sha384-..." + crossorigin="anonymous" +></script> +``` + +Prefer self-hosting critical dependencies. + +Use `rel="noopener noreferrer"` for external new-tab links: + +```html +<a href="https://example.com" target="_blank" rel="noopener noreferrer"> + Example +</a> +``` + +Use iframe sandboxing for untrusted embeds. + +Use Permissions Policy to restrict powerful features: + +```http +Permissions-Policy: geolocation=(), camera=(), microphone=() +``` + +Ask for permissions only in response to user intent. + +Never log sensitive tokens, passwords, personal data, or authorization headers. + +--- + +# Performance + +Measure before optimizing. + +Core concerns: + +- Load less JavaScript. +- Avoid blocking rendering. +- Avoid layout shifts. +- Keep interaction latency low. +- Optimize images. +- Cache effectively. +- Avoid unnecessary re-renders. +- Reduce main-thread work. + +Use semantic HTML and CSS for rendering instead of JavaScript where possible. + +Keep bundles small. + +Prefer route-level and interaction-level code splitting. + +Lazy-load non-critical modules: + +```js +button.addEventListener('click', async () => { + const { openPicker } = await import('./picker.js'); + openPicker(); +}); +``` + +Use `defer` for scripts that do not need to block parsing: + +```html +<script src="/app.js" defer></script> +``` + +Use `type="module"` for module scripts, which are deferred by default: + +```html +<script type="module" src="/app.js"></script> +``` + +Avoid synchronous long tasks. + +Break up heavy work: + +```js +await new Promise((resolve) => setTimeout(resolve, 0)); +``` + +Use `requestIdleCallback` for low-priority work where supported, with fallbacks. + +Use Web Workers for CPU-heavy work. + +Use `requestAnimationFrame` for visual updates: + +```js +requestAnimationFrame(() => { + element.style.transform = `translateX(${x}px)`; +}); +``` + +Batch DOM reads and writes to avoid layout thrashing. + +Bad: + +```js +for (const item of items) { + const height = item.offsetHeight; + item.style.height = `${height + 10}px`; +} +``` + +Better: read first, write later. + +Use `ResizeObserver` instead of polling size: + +```js +const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + // ... + } +}); +observer.observe(element); +``` + +Use `IntersectionObserver` for visibility/lazy work: + +```js +const observer = new IntersectionObserver((entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + loadContent(entry.target); + } + } +}); +``` + +Use `MutationObserver` for DOM change observation instead of repeated scanning. + +Use CSS containment when appropriate: + +```css +.widget { + contain: layout paint; +} +``` + +Use `content-visibility` for large offscreen sections when suitable: + +```css +.section { + content-visibility: auto; + contain-intrinsic-size: 600px; +} +``` + +Optimize images: + +- Use modern formats like WebP and AVIF where appropriate. +- Provide responsive `srcset` and `sizes`. +- Use correct intrinsic dimensions. +- Lazy-load below-the-fold images. +- Avoid shipping huge images resized by CSS. +- Compress assets. + +Preload critical resources carefully: + +```html +<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin> +``` + +Use `font-display: swap` or similar to avoid invisible text: + +```css +@font-face { + font-family: Inter; + src: url('/fonts/inter.woff2') format('woff2'); + font-display: swap; +} +``` + +Use `fetchpriority` where appropriate: + +```html +<img src="hero.jpg" alt="..." fetchpriority="high"> +``` + +Avoid overusing preload and high priority. Incorrect priority hints can harm performance. + +Use HTTP caching headers correctly. + +Use immutable caching for fingerprinted assets: + +```http +Cache-Control: public, max-age=31536000, immutable +``` + +Use short or validated caching for HTML. + +Avoid layout shift: + +- Set image/video dimensions. +- Reserve ad/embed space. +- Avoid injecting content above existing content. +- Use stable fonts or font metric overrides. +- Avoid late-loading banners that push content. + +Use the Performance APIs: + +```js +performance.mark('start'); +// work +performance.mark('end'); +performance.measure('work', 'start', 'end'); +``` + +Use `PerformanceObserver` for metrics where appropriate. + +--- + +# CSS And Rendering Performance + +Prefer compositor-friendly animations: + +```css +.modal { + transition: opacity 150ms ease, transform 150ms ease; +} +``` + +Avoid animating: + +- `height` +- `width` +- `top` +- `left` +- `margin` +- `padding` +- properties that trigger layout or paint heavily + +Use transforms: + +```css +.panel { + transform: translateY(0); +} +``` + +Avoid huge box shadows and filters over large areas. + +Avoid deeply nested selectors in large documents. + +Avoid massive DOMs when virtualization or pagination is more appropriate. + +Use `visibility`, `opacity`, `display`, and `hidden` intentionally. + +`display: none` removes from layout and accessibility tree. + +`visibility: hidden` preserves layout but hides visual content. + +`opacity: 0` keeps layout and can still allow interaction unless disabled. + +Use `hidden` for content that should not be displayed: + +```html +<div hidden>...</div> +``` + +--- + +# Progressive Enhancement + +Start with a working baseline. + +Enhance when features exist: + +```js +if ('showPopover' in HTMLElement.prototype) { + // use popover +} +``` + +Use CSS feature queries: + +```css +@supports (selector(:has(*))) { + // enhanced styles +} +``` + +Avoid browser sniffing: + +```js +// Prefer this +if ('IntersectionObserver' in window) {} +``` + +Use polyfills selectively. + +A good progressive enhancement: + +- Preserves core functionality. +- Uses feature detection. +- Has an acceptable fallback. +- Does not punish unsupported browsers with broken UI. + +Cutting-edge features can be appropriate when: + +- They are progressive enhancement. +- They have a fallback. +- They improve UX without controlling core access. +- They are isolated behind feature detection. +- The code remains understandable. + +Examples of progressive enhancement candidates: + +- Popover API. +- View Transitions API. +- CSS anchor positioning. +- Container/style queries. +- `:has()`. +- `content-visibility`. +- `text-wrap: balance`. +- Declarative Shadow DOM. +- Compression Streams. +- WebGPU, for specialized use cases with fallback to WebGL/Canvas. + +--- + +# Dialog, Popover, And Overlays + +Use native `<dialog>` for dialogs when it matches the desired behavior. + +```html +<dialog id="settings-dialog"> + <form method="dialog"> + <button value="cancel">Cancel</button> + <button value="confirm">Confirm</button> + </form> +</dialog> +``` + +```js +dialog.showModal(); +dialog.close(); +``` + +Use `::backdrop` for modal backdrop styling: + +```css +dialog::backdrop { + background: rgb(0 0 0 / 0.4); +} +``` + +Use the Popover API for non-modal overlays where supported: + +```html +<button popovertarget="menu">Menu</button> +<div id="menu" popover>...</div> +``` + +Use `showPopover()`, `hidePopover()`, and `togglePopover()` where appropriate. + +Use popover as progressive enhancement when unsupported browsers need fallback behavior. + +Do not use dialogs for simple dropdown menus if popover or a normal disclosure is more appropriate. + +Manage focus and dismissal behavior carefully. + +--- + +# Routing And Navigation + +Use normal links for navigation. + +```html +<a href="/settings">Settings</a> +``` + +Use the History API for client-side navigation: + +```js +history.pushState({ page: 'settings' }, '', '/settings'); +``` + +Handle `popstate`: + +```js +window.addEventListener('popstate', () => { + renderRoute(location.pathname); +}); +``` + +Prefer server-compatible routes so reloads and deep links work. + +Use URL as state for shareable, restorable state: + +- current route +- filters +- search query +- pagination +- selected tab when useful + +Use `URLSearchParams` for query state. + +Do not hide important app state only in memory. + +Handle scroll restoration deliberately: + +```js +history.scrollRestoration = 'manual'; +``` + +Use hash navigation for intra-page anchors when appropriate. + +--- + +# State Management + +Keep state minimal and explicit. + +Prefer deriving values instead of duplicating state. + +Bad: + +```js +let items = []; +let itemCount = 0; +``` + +Better: + +```js +const itemCount = items.length; +``` + +Keep state close to where it is used unless shared ownership is clear. + +Separate: + +- Server state. +- URL state. +- Form state. +- UI-only ephemeral state. +- Cached derived state. + +Avoid global mutable state unless it is intentional and constrained. + +Use immutable updates when it improves predictability: + +```js +const nextItems = items.map((item) => + item.id === id ? { ...item, done: true } : item, +); +``` + +Avoid deep mutation across module boundaries. + +Design state transitions explicitly for complex flows. + +Use finite-state modeling for workflows with clear statuses. + +Avoid boolean explosions: + +```js +// Hard to reason about +isLoading +isSaving +hasError +isComplete +``` + +Prefer a single status where appropriate: + +```js +status: 'idle' | 'loading' | 'success' | 'error' +``` + +--- + +# Error Handling + +Handle errors at the appropriate boundary. + +User-facing errors should be: + +- Clear. +- Specific enough to act on. +- Non-technical unless the user is technical. +- Accessible. +- Recoverable where possible. + +Developer logs should include context. + +Do not swallow errors silently. + +Do not expose sensitive implementation details to users. + +Use retries only for operations that are safe to retry. + +Use exponential backoff for repeated network retries. + +Distinguish: + +- Validation errors. +- Network errors. +- Permission errors. +- Not found errors. +- Server errors. +- Unexpected bugs. + +Use fallback UI for failed async content. + +Clean up loading state in `finally`: + +```js +isLoading = true; + +try { + await save(); +} finally { + isLoading = false; +} +``` + +--- + +# Web APIs + +## URL + +Use `URL` for parsing and constructing URLs. + +```js +const url = new URL(requestUrl); +const id = url.searchParams.get('id'); +``` + +## Clipboard + +Use the async Clipboard API in response to user action: + +```js +await navigator.clipboard.writeText(text); +``` + +Handle permission and failure. + +## File APIs + +Use file inputs for user-selected files: + +```html +<input type="file" accept="image/*"> +``` + +Use `FileReader` or newer Blob methods: + +```js +const text = await file.text(); +const buffer = await file.arrayBuffer(); +``` + +Use object URLs for local previews: + +```js +const url = URL.createObjectURL(file); +image.src = url; +URL.revokeObjectURL(url); +``` + +Revoke object URLs when no longer needed. + +## Drag And Drop + +Use drag/drop APIs carefully and accessibly. + +Always provide a non-drag alternative, such as file input or buttons. + +## Canvas + +Use `<canvas>` for immediate-mode graphics. + +Account for device pixel ratio: + +```js +const dpr = window.devicePixelRatio || 1; +canvas.width = Math.floor(width * dpr); +canvas.height = Math.floor(height * dpr); +context.scale(dpr, dpr); +``` + +Use OffscreenCanvas in workers for heavy rendering where supported. + +## Web Workers + +Use workers for CPU-heavy tasks: + +```js +const worker = new Worker('/worker.js', { type: 'module' }); +worker.postMessage(data); +``` + +Use transferable objects for large binary data: + +```js +worker.postMessage(buffer, [buffer]); +``` + +## BroadcastChannel + +Use `BroadcastChannel` for same-origin tab communication: + +```js +const channel = new BroadcastChannel('app'); +channel.postMessage({ type: 'logout' }); +``` + +## Storage Events + +Use `storage` events for cross-tab localStorage updates. + +## WebSocket + +Use WebSocket for bidirectional real-time communication. + +Handle: + +- reconnects +- backoff +- heartbeats +- duplicate messages +- ordering +- authentication +- graceful close + +## Server-Sent Events + +Use EventSource for one-way server-to-client streams: + +```js +const events = new EventSource('/events'); +events.addEventListener('message', (event) => { + console.log(event.data); +}); +``` + +## Streams + +Use Streams for large or incremental data. + +```js +const reader = response.body.getReader(); +``` + +Use streams when avoiding full buffering matters. + +## Encoding + +Use `TextEncoder` and `TextDecoder`: + +```js +const bytes = new TextEncoder().encode(text); +const text = new TextDecoder().decode(bytes); +``` + +## Crypto + +Use Web Crypto API for cryptographic operations. + +```js +const id = crypto.randomUUID(); +const bytes = crypto.getRandomValues(new Uint8Array(16)); +``` + +Use `crypto.randomUUID()` for UUIDs. + +Do not implement your own cryptography. + +Do not use `Math.random()` for security-sensitive randomness. + +--- + +# Animation + +Use CSS transitions/animations for simple UI animation. + +Use Web Animations API for dynamic animation control: + +```js +const animation = element.animate( + [ + { opacity: 0, transform: 'translateY(8px)' }, + { opacity: 1, transform: 'translateY(0)' }, + ], + { + duration: 150, + easing: 'ease-out', + }, +); +``` + +Respect reduced motion: + +```js +const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches; +``` + +Avoid unnecessary motion. + +Do not animate essential content in ways that block use. + +Use View Transitions API as progressive enhancement for route/page transitions where supported. + +--- + +# Responsive Design + +Design fluid layouts. + +Use: + +- Flexible grids. +- Relative units. +- Container queries. +- Media queries where page-level breakpoints are needed. +- Intrinsic sizing. +- `minmax()`, `auto-fit`, and `auto-fill`. + +Example: + +```css +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); + gap: 1rem; +} +``` + +Avoid fixed pixel widths for major layouts. + +Use `max-inline-size` for readable text: + +```css +.article { + max-inline-size: 70ch; +} +``` + +Use `rem` for spacing and typography that should respect user settings. + +Use touch-friendly targets. Interactive controls should be large enough to tap. + +Avoid hover-only interactions. + +Use pointer/hover media queries: + +```css +@media (hover: hover) and (pointer: fine) { + .button:hover { + background: var(--hover-bg); + } +} +``` + +Make content work in narrow, wide, zoomed, and high-density environments. + +Test at 200% zoom. + +Avoid horizontal scrolling unless it is the intended interaction. + +--- + +# Internationalization + +Use `lang`. + +Use logical CSS properties. + +Avoid hard-coded assumptions about: + +- Text length. +- Word boundaries. +- Date format. +- Time format. +- Number format. +- Currency format. +- Name format. +- Address format. +- Text direction. +- Plurals. + +Use `Intl`: + +```js +new Intl.NumberFormat(locale).format(number); +new Intl.DateTimeFormat(locale).format(date); +new Intl.RelativeTimeFormat(locale).format(value, unit); +new Intl.ListFormat(locale).format(items); +new Intl.PluralRules(locale).select(count); +``` + +Support RTL by avoiding left/right where inline-start/inline-end works. + +Do not concatenate translated sentence fragments when grammar may vary. + +--- + +# Clean Code Principles + +Write code for readers. + +Prefer clarity over cleverness. + +Use descriptive names: + +```js +const pendingInvitations = invitations.filter((invitation) => !invitation.accepted); +``` + +Avoid vague names: + +```js +data +thing +stuff +obj +tmp +``` + +Except in tiny local contexts where obvious. + +Functions should generally do one thing. + +Keep functions small enough to understand, but do not fragment code into meaningless wrappers. + +Prefer early returns to deep nesting: + +```js +function getDisplayName(user) { + if (!user) return 'Guest'; + if (user.displayName) return user.displayName; + return user.email; +} +``` + +Avoid hidden side effects. + +Separate pure transformations from I/O. + +Make invalid states hard to represent. + +Avoid boolean parameters that obscure intent: + +```js +// Less clear +setModal(true); + +// Clearer +openModal(); +closeModal(); +``` + +Use guard clauses for invalid input. + +Avoid mutation unless ownership is clear. + +Avoid premature abstraction. + +Abstract after a real pattern appears, not before. + +Keep module boundaries meaningful. + +Avoid dependency cycles. + +Prefer composition over inheritance for app code. + +Keep public APIs small. + +Use comments to explain why, not what. + +Good: + +```js +// The API returns local dates without a timezone, so parse as local time. +``` + +Bad: + +```js +// Increment i by one. +``` + +Delete dead code. + +Avoid commented-out code. + +Use formatting tools. + +Use linting for consistency and bug prevention. + +Use tests for behavior, not implementation details. + +--- + +# API Design + +Design APIs around use cases, not internal implementation. + +Prefer explicit parameter objects when there are several options: + +```js +createUser({ + name, + email, + role, +}); +``` + +Avoid positional boolean arguments: + +```js +// Bad +createUser(name, true, false); +``` + +Return consistent shapes. + +Use errors for exceptional failure and result objects for expected domain outcomes when appropriate. + +Example: + +```js +return { ok: true, value }; +return { ok: false, error }; +``` + +Keep async APIs consistently async. + +Avoid functions that sometimes return a promise and sometimes do not. + +Document units: + +```js +timeoutMs +sizeBytes +``` + +Be explicit about ownership and mutation: + +```js +function sortUsers(users) { + return [...users].sort(compareUsers); +} +``` + +Avoid surprising global behavior. + +--- + +# Testing + +Use automated tests for important behavior. + +Test public behavior, not private implementation. + +Use unit tests for pure logic. + +Use integration tests for component interactions. + +Use end-to-end tests for critical user flows. + +Test accessibility basics: + +- Keyboard navigation. +- Accessible names. +- Focus management. +- Form labels. +- Error announcements. +- Color contrast where tooling supports it. + +Test failure states: + +- Network failure. +- Empty data. +- Loading state. +- Permission denied. +- Validation errors. +- Slow responses. +- Partial data. + +Use deterministic tests. + +Avoid real timers when fake timers are appropriate. + +Avoid arbitrary sleeps in tests. Wait for observable conditions. + +Mock network boundaries, not internal implementation details, unless there is a strong reason. + +--- + +# Build And Dependency Hygiene + +Prefer minimal dependencies. + +Before adding a dependency, consider: + +- Is there a native API? +- Is the package actively maintained? +- Is the bundle cost justified? +- Does it support ESM/tree-shaking? +- Does it introduce security or supply-chain risk? +- Can the problem be solved with a small local function? + +Use lockfiles. + +Keep dependencies updated. + +Avoid depending on large libraries for tiny utilities. + +Prefer modern ESM packages. + +Avoid shipping development-only code to production. + +Use code splitting thoughtfully. + +Use source maps appropriately. Be careful exposing source maps for sensitive code, though frontend code is never truly secret. + +Use environment variables only for non-secret frontend configuration. Anything shipped to the browser is public. + +--- + +# Browser Compatibility + +Target evergreen browsers unless product requirements say otherwise. + +Use feature detection: + +```js +if ('ResizeObserver' in window) { + // ... +} +``` + +Use `@supports` for CSS. + +Use transpilation/polyfills based on actual support targets. + +Do not blindly transpile everything to very old JavaScript if the target browsers are modern; it can increase bundle size. + +Understand that polyfills cannot fully emulate every platform feature, especially layout, security, permissions, and performance APIs. + +Use progressive enhancement for newer features. + +--- + +# Common Modern Features I Would Treat As Standard Or Near-Standard + +Broadly standard in modern web work: + +- ES modules. +- `async` / `await`. +- `fetch`. +- `Promise`. +- `URL`. +- `URLSearchParams`. +- `AbortController`. +- `CustomEvent`. +- `classList`. +- `dataset`. +- `closest`. +- `matches`. +- `template`. +- Shadow DOM and Custom Elements when needed. +- CSS Grid. +- Flexbox. +- CSS custom properties. +- CSS logical properties. +- `gap`. +- `aspect-ratio`. +- `:focus-visible`. +- `:is()`. +- `:where()`. +- `@supports`. +- `prefers-reduced-motion`. +- `prefers-color-scheme`. +- `Intl`. +- `crypto.randomUUID`. +- `structuredClone`. +- `ResizeObserver`. +- `IntersectionObserver`. +- `MutationObserver`. +- `localStorage`, `sessionStorage`, IndexedDB. +- Service workers where appropriate. +- Web Workers where appropriate. + +Modern features I would use with feature detection or fallback depending on project requirements: + +- `:has()`. +- Container queries. +- CSS nesting. +- Cascade layers. +- `content-visibility`. +- `text-wrap: balance`. +- Popover API. +- Native `<dialog>`. +- View Transitions API. +- Anchor positioning. +- `inert`. +- Declarative Shadow DOM. +- WebGPU. +- Compression Streams. +- File System Access API. +- Web Share API. +- Async Clipboard API. +- Navigation API. + +--- + +# HTML/CSS/JS Integration + +Use classes for styling hooks. + +Use `data-*` for behavior hooks when useful. + +Avoid binding JavaScript behavior to purely presentational class names if those names are likely to change. + +Example: + +```html +<button class="button button--danger" data-action="delete"> + Delete +</button> +``` + +Use attributes for state that CSS and accessibility need: + +```html +<button aria-expanded="false" data-menu-button> + Menu +</button> +``` + +Keep CSS, behavior, and semantics aligned. + +Do not put business logic in HTML attributes. + +Avoid inline styles except for dynamic values that are genuinely instance-specific, often via custom properties: + +```js +element.style.setProperty('--progress', `${progress}%`); +``` + +```css +.progress { + inline-size: var(--progress); +} +``` + +--- + +# Accessibility Patterns + +## Disclosure + +```html +<button aria-expanded="false" aria-controls="panel"> + Details +</button> +<div id="panel" hidden> + ... +</div> +``` + +Update both `hidden` and `aria-expanded`. + +## Tabs + +Use native links if tabs are actually navigation. + +For true in-page tabs, implement: + +- `role="tablist"` +- `role="tab"` +- `role="tabpanel"` +- Arrow-key navigation +- Selected state +- Focus behavior + +Do not build custom tabs unless needed. + +## Menu Buttons + +Use menu semantics only for application-style command menus. + +For site navigation, normal links are usually better. + +## Toasts + +Use `role="status"` or an aria-live region. + +Do not move focus to passive toast messages. + +Provide persistent access to important messages. + +## Modals + +Use native dialog where possible. + +Ensure: + +- Accessible name. +- Initial focus. +- Escape closes unless destructive flow requires otherwise. +- Return focus. +- Background is inert. +- Scrolling is controlled. + +--- + +# Data Fetching And UI States + +Every async UI should consider: + +- Idle state. +- Loading state. +- Success state. +- Empty state. +- Error state. +- Retry. +- Cancellation. +- Race conditions. + +Avoid stale responses overwriting newer ones. + +Example: + +```js +let requestId = 0; + +async function load(query) { + const id = ++requestId; + + const response = await fetch(`/search?q=${encodeURIComponent(query)}`); + const results = await response.json(); + + if (id !== requestId) return; + + render(results); +} +``` + +Use `AbortController` to cancel obsolete requests. + +```js +let controller; + +async function load(query) { + controller?.abort(); + controller = new AbortController(); + + const response = await fetch(`/search?q=${encodeURIComponent(query)}`, { + signal: controller.signal, + }); + + render(await response.json()); +} +``` + +--- + +# Time, Timers, And Scheduling + +Use `setTimeout` for delayed work. + +Use `setInterval` carefully; clear it when no longer needed. + +Prefer recursive `setTimeout` for polling to avoid overlapping calls: + +```js +async function poll() { + await update(); + setTimeout(poll, 5000); +} +``` + +Use `requestAnimationFrame` for frame-aligned visual updates. + +Use `queueMicrotask` for microtask scheduling when needed: + +```js +queueMicrotask(() => { + // runs after current task, before next render opportunity +}); +``` + +Do not use microtasks for heavy work. + +Use `scheduler.postTask` only as progressive enhancement where supported. + +--- + +# Observers + +Use `IntersectionObserver` for visibility. + +Use `ResizeObserver` for element size. + +Use `MutationObserver` for DOM mutations. + +Disconnect observers when no longer needed: + +```js +observer.disconnect(); +``` + +Avoid observing huge subtrees unnecessarily. + +Debounce or batch expensive observer callbacks. + +--- + +# Input And Interaction + +Use pointer events for unified mouse/touch/pen handling: + +```js +element.addEventListener('pointerdown', onPointerDown); +``` + +Use pointer capture for dragging: + +```js +element.setPointerCapture(event.pointerId); +``` + +Support keyboard equivalents for pointer interactions. + +Do not rely on hover for essential actions. + +Use `touch-action` for custom gestures: + +```css +.slider { + touch-action: pan-y; +} +``` + +Prevent default only when necessary. + +--- + +# Clipboard, Share, Permissions + +Use permission-gated APIs only after user intent. + +Bad: + +```js +navigator.geolocation.getCurrentPosition(...); // immediately on load +``` + +Better: + +```js +button.addEventListener('click', requestLocation); +``` + +Handle denial and unsupported cases. + +Use Web Share API progressively: + +```js +if (navigator.share) { + await navigator.share({ title, url }); +} +``` + +Fallback to copying or showing the URL. + +--- + +# Media + +Use native media elements: + +```html +<video controls playsinline preload="metadata"> + <source src="video.webm" type="video/webm"> + <source src="video.mp4" type="video/mp4"> +</video> +``` + +Use captions: + +```html +<track kind="captions" src="captions.vtt" srclang="en" label="English"> +``` + +Avoid autoplay with sound. + +Use `playsinline` for mobile video. + +Respect reduced motion and data usage where relevant. + +--- + +# SEO And Metadata + +Use semantic HTML and meaningful document structure. + +Set page titles: + +```html +<title>Account settings +``` + +Use meta description where appropriate: + +```html + +``` + +Use canonical URLs when needed: + +```html + +``` + +Use structured data only when accurate. + +Use server-rendered or statically rendered content for public SEO-critical pages when possible. + +Ensure links are crawlable with real `href` values. + +Do not rely on click handlers for navigation on public pages. + +--- + +# Privacy + +Collect the minimum data needed. + +Avoid third-party scripts unless justified. + +Disclose tracking and analytics where required. + +Respect browser privacy features. + +Avoid fingerprinting. + +Do not store sensitive personal data unnecessarily. + +Give users control over permissions and data where appropriate. + +--- + +# Common Anti-Patterns + +Avoid: + +```js +document.body.innerHTML = userContent; +``` + +Avoid: + +```html +
Save
+``` + +Avoid: + +```css +* { + outline: none; +} +``` + +Avoid: + +```js +JSON.parse(JSON.stringify(complexObject)); +``` + +Avoid: + +```js +setTimeout(() => { + assumeThingIsReady(); +}, 1000); +``` + +Avoid: + +```js +if (navigator.userAgent.includes('Chrome')) { + // ... +} +``` + +Avoid: + +```js +catch (error) {} +``` + +Avoid shipping large dependencies for trivial utilities. + +Avoid using ARIA to fake native controls when native controls work. + +Avoid absolute positioning as a primary layout system. + +Avoid fixed heights for text containers unless overflow is handled. + +Avoid relying on color alone. + +Avoid storing JWTs or long-lived secrets in `localStorage`. + +Avoid blocking the main thread with large synchronous loops. + +Avoid making every component globally stateful. + +Avoid abstractions that hide simple platform behavior. + +--- + +# Code Style Defaults + +Use consistent formatting. + +Prefer single responsibility per file/module. + +Sort imports consistently. + +Group related code. + +Keep public API at the top or clearly exposed. + +Avoid large files with unrelated concerns. + +Use explicit return values. + +Prefer positive conditions: + +```js +if (isValid) { + submit(); +} +``` + +Instead of hard-to-read negated branches. + +Use constants for repeated magic values: + +```js +const MAX_UPLOAD_BYTES = 10_000_000; +``` + +Use enums or string unions for known statuses when using TypeScript. + +Keep configuration centralized when values are shared. + +Avoid mixing units. + +Use names that encode units: + +```js +delayMs +widthPx +sizeBytes +``` + +--- + +# Practical Defaults + +For a modern browser-native feature, I would usually default to this checklist: + +- Semantic HTML first. +- Native controls first. +- CSS Grid/Flexbox for layout. +- CSS custom properties for reusable values. +- Logical properties for spacing. +- `fetch` with explicit error handling. +- `AbortController` for cancelable async work. +- `URL`/`URLSearchParams` for URL manipulation. +- `Intl` for formatting. +- `textContent` for untrusted text. +- Event delegation for repeated elements. +- `addEventListener` with cleanup. +- `ResizeObserver`/`IntersectionObserver` instead of polling. +- `FormData` and constraint validation for forms. +- `dialog`/popover where appropriate, progressively enhanced. +- Accessibility through native semantics, labels, focus, keyboard support. +- Feature detection for newer APIs. +- Progressive enhancement for cutting-edge APIs. +- Minimal dependencies. +- Small cohesive modules. +- Explicit state and error states. +- Tests around behavior and critical flows. + +This is the baseline “common knowledge” I would bring to modern web development before reading any local guide. diff --git a/.agents/skills/project-discipline-guides/mirrors/webmcp/gemini_mirror.md b/.agents/skills/project-discipline-guides/mirrors/webmcp/gemini_mirror.md new file mode 100644 index 000000000..20cc6adba --- /dev/null +++ b/.agents/skills/project-discipline-guides/mirrors/webmcp/gemini_mirror.md @@ -0,0 +1,108 @@ +# Modern Web Development: Redundancy Mirror (Common Knowledge) + +This document serves as a comprehensive "Redundancy Mirror," documenting the standard best practices, APIs, and syntax I natively understand for modern web development (Web Platform/WebMCP). + +--- + +## 1. Modern JavaScript (ECMAScript) + +### Core Language Features +* **Optional Chaining & Nullish Coalescing**: Use `?.` and `??` for safer property access and default value assignment instead of verbose logical `&&` or `||` checks. +* **Asynchronous Patterns**: Prefer `async/await` over raw `Promise.then()` chains. +* **Advanced Promise APIs**: + * `Promise.allSettled()` for handling multiple independent requests where partial success is acceptable. + * `Promise.any()` for racing multiple sources for the first success. + * `Promise.withResolvers()` (Baseline 2024) for creating a promise with its resolution/rejection functions in the same scope. +* **Data Structures**: Use `Map` and `Set` for key-value pairs and unique collections when performance or non-string keys are required. Use `WeakMap` and `WeakSet` for memory-safe object associations. +* **Functional Array Methods**: Use non-mutating methods like `toSorted()`, `toReversed()`, and `toSpliced()` (Baseline 2023) to maintain immutability. +* **Grouping**: Use `Object.groupBy()` and `Map.groupBy()` for efficient data categorization. +* **Deep Cloning**: Use `structuredClone()` for native deep copies of objects (including Dates, RegEx, and Arrays) instead of `JSON.parse(JSON.stringify())`. + +### Modules & Architecture +* **ES Modules (ESM)**: Use standard `import`/`export`. Favor named exports for better tree-shaking and discoverability. +* **Top-level await**: Use directly in modules to simplify initialization logic. +* **Dynamic Imports**: Use `import()` for code splitting and lazy-loading non-critical paths. + +--- + +## 2. CSS Architecture & Modern Features + +### Layout & Selection +* **Grid & Flexbox**: Use Flexbox for 1D layouts and Grid for 2D layouts. Use `gap` (supported in both) for spacing. +* **Subgrid**: Use `grid-template-columns: subgrid` to align nested elements with the parent grid. +* **The `:has()` Selector**: The "parent selector." Use it for conditional styling based on child states (e.g., `.card:has(img)`). +* **Container Queries**: Use `@container` to style elements based on the size of their parent container rather than the viewport. +* **Logical Properties**: Use `margin-inline`, `padding-block`, `inset-inline-start` instead of `left`/`right`/`top`/`bottom` to support multi-directional layouts (LTR/RTL) automatically. + +### Organization & Modern Syntax +* **Native Nesting**: Use browser-native CSS nesting to group related styles without preprocessors like Sass. +* **Cascade Layers (`@layer`)**: Use layers to manage specificity and prevent third-party styles from overriding application-level defaults. +* **Variables (Custom Properties)**: Use `--var` for tokens and dynamic runtime styling. Define defaults with `var(--name, fallback)`. +* **Color Level 4/5**: Use `oklch()` or `oklab()` for perceptually uniform colors. Use `color-mix()` for dynamic shading and blending. + +--- + +## 3. Web Platform APIs (Standard Library) + +### DOM & Interactivity +* **Intersection Observer**: Use for lazy-loading images, infinite scroll, or triggering animations when elements enter the viewport. +* **Resize Observer**: Use for responding to element size changes (more granular than `window.onresize`). +* **Mutation Observer**: Use for monitoring changes to the DOM tree (attributes, children). +* **Popover API**: Use the `popover` attribute for tooltips, menus, and overlays without managing z-index manually. +* **Dialog API**: Use `` and `.showModal()` for accessible, focus-trapped modal windows. + +### Data & State +* **Fetch API**: Use for network requests with `AbortController` for cancellation. +* **Storage**: + * `localStorage` for persistent simple data. + * `IndexedDB` for complex, large-scale client-side data. +* **Streams API**: Use for processing large data chunks (e.g., video, large JSON) without loading everything into memory. +* **Web Workers**: Use for offloading heavy computation (image processing, data crunching) to keep the main thread responsive. + +### Progressive Enhancements (Cutting Edge) +* **View Transitions API**: Use `document.startViewTransition()` for smooth, app-like transitions between page states. +* **Scroll-driven Animations**: Use `scroll-timeline` or `view-timeline` for animations tied to scroll position without JS scroll listeners. +* **Speculation Rules**: Use ` -``` - #### Cross-Origin Resource Sharing (CORS) CORS is a permission grant, not a defense — it tells the browser which cross-origin reads to allow. The risk is misconfiguring it as too permissive. - **DO**: Validate the `Origin` header on the server and set `Access-Control-Allow-Origin` dynamically to specific origins (rather than wildcard `*`). -- **DO NOT**: Use wildcard `*` for `Access-Control-Allow-Origin` if `Access-Control-Allow-Credentials: true` is required — the browser will reject the response. - **DO**: Handle preflight (`OPTIONS`) requests by returning appropriate headers before processing data. ```http