Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Accessible Forms

ASecurity

Production form accessibility — labels, errors, autocomplete, validation timing, multi-step flows, file inputs, date pickers, and the WCAG 2.2 Redundant Entry + Accessible Authentication SCs.

12 stars
0 votes
0 copies
0 views
Added 9/28/2026
ai-agentsgoreactgitapifrontend

Works with

cliapi

Security Analysis

A100/100

Scanned 9/28/2026

Install to Claude Code

$npx -y skills add Nmor/the-claude-council --skill accessible-forms --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Accessible Forms?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Accessible Forms
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nmor-accessible-forms/badge)](https://www.skillsdirectory.com/skills/nmor-accessible-forms)

More formats (shields.io, HTML) on the badges page.

Files
SKILL.md
---
name: accessible-forms
description: Production form accessibility — labels, errors, autocomplete, validation timing, multi-step flows, file inputs, date pickers, and the WCAG 2.2 Redundant Entry + Accessible Authentication SCs.
---

# Accessible Forms

> **Size budget: 20 KB** — `token-budget.mjs --check`.

The form is where accessibility most often breaks. Visual designers prioritise clean layouts;
engineers reach for `placeholder` as a label; validation surfaces errors at submit; keyboard users
get trapped in modal flows; screen-reader users discover required fields after submission. This
skill encodes the patterns that pass real audits.

## Purpose

Forms are conversion-critical (signup, checkout, contact, KYC) AND accessibility-critical (auth,
payment, account management are AAA paths per `wcag-accessibility`). The patterns below cover every
input type, every state transition, every validation pattern, and the two new WCAG 2.2 SCs that
specifically target authentication forms.

## Standards Cited

- **WCAG 2.2 §1.3.1** — Info and Relationships (label association)
- **WCAG 2.2 §1.3.5** — Identify Input Purpose (autocomplete tokens)
- **WCAG 2.2 §2.4.6** — Headings and Labels (descriptive)
- **WCAG 2.2 §3.3.1** — Error Identification
- **WCAG 2.2 §3.3.2** — Labels or Instructions
- **WCAG 2.2 §3.3.3** — Error Suggestion
- **WCAG 2.2 §3.3.4** — Error Prevention (Legal, Financial, Data)
- **WCAG 2.2 §3.3.7** — Redundant Entry (NEW in 2.2)
- **WCAG 2.2 §3.3.8** — Accessible Authentication Minimum (NEW in 2.2)
- **WCAG 2.2 §3.3.9** — Accessible Authentication Enhanced (NEW in 2.2)
- **HTML Living Standard** — `<label>`, `<input>`, `autocomplete` attribute, constraint validation
  API
- **WAI-ARIA 1.2** — `aria-required`, `aria-invalid`, `aria-describedby`, `aria-errormessage`
- **WHATWG Autocomplete tokens** — `username`, `current-password`, `new-password`, `one-time-code`,
  `email`, etc.

## When to Fire

- Any `<form>`, `<input>`, `<select>`, `<textarea>`, `<button type="submit">`
- Any signup / login / password reset / 2FA flow
- Any checkout / billing / payment flow
- Any KYC / identity verification flow
- Any multi-step wizard
- Any inline editing pattern (double-click to edit, etc.)

## Core Patterns

### Every input has a real `<label>`

```html
<!-- WRONG — placeholder is not a label -->
<input type="email" placeholder="Email address" />

<!-- WRONG — visually hidden but no association -->
<label>Email address</label>
<input type="email" />

<!-- RIGHT — `for` matches input `id` -->
<label for="signup-email">Email address</label>
<input
  type="email"
  id="signup-email"
  name="email"
  autocomplete="email"
  required
  aria-required="true"
/>

<!-- RIGHT (alternative) — label wraps input -->
<label>
  Email address
  <input type="email" name="email" autocomplete="email" required />
</label>
```

When the label must be visually hidden (e.g., search input in a header), use the `sr-only` pattern —
NOT `aria-label` (which loses the visible label when AT switches to braille / sound only):

```html
<form role="search">
  <label for="search" class="sr-only">Search articles</label>
  <input type="search" id="search" name="q" placeholder="Search" />
  <button type="submit">Search</button>
</form>
```

### Autocomplete tokens (WCAG 1.3.5)

Browsers + password managers + autofill need to identify input purpose. Use the WHATWG token list:

```html
<input type="text"     name="given-name"  autocomplete="given-name" />
<input type="text"     name="family-name" autocomplete="family-name" />
<input type="email"    name="email"       autocomplete="email" />
<input type="tel"      name="phone"       autocomplete="tel" />
<input type="password" name="password"    autocomplete="current-password" />
<input type="password" name="new-password" autocomplete="new-password" />
<input type="text"     name="otp"         autocomplete="one-time-code" />

<!-- Address -->
<input type="text" name="street"   autocomplete="street-address" />
<input type="text" name="city"     autocomplete="address-level2" />
<input type="text" name="region"   autocomplete="address-level1" />
<input type="text" name="postal"   autocomplete="postal-code" />
<input type="text" name="country"  autocomplete="country-name" />

<!-- Payment -->
<input type="text" name="cc-name"   autocomplete="cc-name" />
<input type="text" name="cc-number" autocomplete="cc-number" inputmode="numeric" />
<input type="text" name="cc-exp"    autocomplete="cc-exp" />
<input type="text" name="cc-csc"    autocomplete="cc-csc" inputmode="numeric" />
```

Disabling autocomplete (`autocomplete="off"`) on auth fields fails WCAG 1.3.5 AND fights password
managers — banned outside narrow cases (e.g., shared kiosk).

### Validation timing — three correct moments

1. **On blur** for format checks ("Invalid email") — debounce 300ms; don't fire while user is typing
2. **On change** for cross-field checks ("Passwords don't match") — only after both fields blurred
   once
3. **On submit** for server-side checks (email unique, payment authorised)

Banned:

- **On every keystroke** — user types one character, sees "Required" — destroys flow
- **Only on submit** — user fills form, submits, sees 8 errors — has to scroll back through

### Error patterns (ARIA-anchored)

```html
<div class="field">
  <label for="email">Email address</label>
  <input
    type="email"
    id="email"
    name="email"
    autocomplete="email"
    required
    aria-required="true"
    aria-invalid="true"
    aria-describedby="email-help email-error"
  />
  <p id="email-help" class="hint">We'll never share your email.</p>
  <p id="email-error" class="error" role="alert">
    Enter a valid email address — e.g., name@example.com.
  </p>
</div>
```

- `aria-invalid="true"` flips when the field is invalid; flip back to `"false"` once corrected
- `aria-describedby` joins multiple ids (hint + error); the error message reads on focus
- `role="alert"` announces the error immediately when it appears (use `aria-live="polite"` if the
  error should NOT interrupt — e.g., for non-blocking warnings)

### Error message content

| Wrong | Right |
| --- | --- |
| "Invalid" | "Enter a valid email address — e.g., <name@example.com>" |
| "Field required" | "Enter your full legal name" |
| "Must be 8+ characters" | "Use 12+ characters including a number and a symbol" |
| "Password too weak" | "Add a number, a symbol, and increase length to 12+" |
| Red border, no text | Red border + text describing what's wrong + how to fix |

WCAG 3.3.3 — error messages MUST suggest a fix when feasible.

### WCAG 3.3.7 — Redundant Entry

```html
<!-- WRONG — re-asking the email at the end of a multi-step signup -->
Step 1: Enter email
Step 2: Choose plan
Step 3: Enter billing details
Step 4: Confirm email (AGAIN)

<!-- RIGHT — auto-fill from earlier step; show + allow edit -->
Step 4: Confirm
  Email: alice@example.com  [Edit]
  Plan: Pro
  Billing: ...
```

The user enters info ONCE per session. If you need confirmation, SHOW + allow edit; don't re-type.

### WCAG 3.3.8 — Accessible Authentication

Cognitive function tests (memorising passwords, solving puzzles, transcribing characters) must have
an accessible alternative:

| Auth method | 3.3.8 (AA) status |
| --- | --- |
| Password | ✅ if password managers ALLOWED (don't block paste; honour `autocomplete`) |
| Password manager | ✅ canonical accessible alternative |
| Magic link | ✅ no memorisation required |
| Passkey / WebAuthn | ✅ device authentication |
| Biometric (Touch ID / Face ID) | ✅ no cognitive test |
| SMS OTP | ✅ if user can copy-paste the code |
| TOTP (Google Authenticator) | ⚠️ AA-passing if user can copy-paste; AAA needs alternative |
| CAPTCHA (text recognition) | ❌ FAILS 3.3.8 AA without alternative |
| Image puzzles ("find the bus") | ❌ FAILS unless alternative offered |
| Math problems | ❌ FAILS |

If you ship a CAPTCHA, provide an accessible alternative (audio CAPTCHA, email link, support
contact).

### Required vs optional

```html
<!-- Mark REQUIRED visually + programmatically -->
<label for="name">
  Full name
  <span aria-hidden="true" class="required-mark">*</span>
</label>
<input
  type="text"
  id="name"
  name="name"
  required
  aria-required="true"
  autocomplete="name"
/>

<!-- Form-level legend explaining the asterisk -->
<p class="form-help">
  Fields marked <span aria-hidden="true">*</span><span class="sr-only">required</span> are required.
</p>
```

NEVER use only color (red border) to indicate required — WCAG 1.4.1.

### `<fieldset>` + `<legend>` for grouped inputs

```html
<fieldset>
  <legend>Shipping address</legend>
  <!-- street, city, postal — all inputs INSIDE this fieldset -->
</fieldset>

<fieldset>
  <legend>How should we contact you?</legend>
  <label><input type="radio" name="contact" value="email" /> Email</label>
  <label><input type="radio" name="contact" value="sms" /> SMS</label>
  <label><input type="radio" name="contact" value="phone" /> Phone call</label>
</fieldset>
```

Screen readers announce the legend before each input within the fieldset — context for what the
field belongs to.

### Multi-step forms

- Show progress: "Step 2 of 4 — Billing details"
- Each step has its own `<h1>` or `<h2>` (focus moves on step transition)
- Allow back navigation; preserve already-entered data
- Save draft state if the form is long
- Use `aria-current="step"` on the active progress indicator

### File inputs

```html
<label for="resume">Upload resume (PDF, DOCX — max 10MB)</label>
<input
  type="file"
  id="resume"
  name="resume"
  accept=".pdf,.docx,application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
  aria-describedby="resume-help"
/>
<p id="resume-help" class="hint">We accept PDF and Word documents up to 10MB.</p>
```

After upload, announce the filename via `aria-live`:

```tsx
<div aria-live="polite" aria-atomic="true" className="sr-only">
  {uploadedFileName ? `Uploaded: ${uploadedFileName}` : ''}
</div>
```

### Date pickers

Native `<input type="date">` is accessible by default. Custom date pickers (React DayPicker, etc.)
MUST implement the APG dialog + grid keyboard model:

- Arrow keys move between days
- Page Up / Page Down = month
- Shift + Page Up / Page Down = year
- Home / End = start / end of week
- Esc closes
- Enter selects

If you ship a custom picker, also provide a typed input fallback: "Or type the date as YYYY-MM-DD".

### Submit button states

| State | Pattern |
| --- | --- |
| Idle | `<button type="submit">Submit</button>` |
| Submitting | `<button type="submit" disabled aria-busy="true">Submitting…</button>` |
| After error | Re-enable; focus the first invalid field; announce "Form has errors" via `role="alert"` |
| After success | Navigate to success page OR show inline success with focus moved to it |

Banned: spinner that spins forever with no announcement.

## Anti-Patterns

### Anti-pattern 1: Placeholder as label

Placeholders disappear when the field has a value. Users with cognitive disabilities, ADHD, or
simply tabbing back to verify lose the field's purpose. Always use a real `<label>`.

### Anti-pattern 2: Disabling form fields without explanation

`<input disabled>` is invisible to most screen readers OR announced confusingly. If a field is
conditionally available, prefer hiding it entirely (with `aria-live` announcing the change) or
showing it readonly with explanation: "Country: United States (set during signup; contact support to
change)".

### Anti-pattern 3: Errors at the top of the form only

Inline errors per-field are more actionable. If you ALSO show a summary, link from the summary to
each field via `<a href="#field-id">`.

### Anti-pattern 4: Custom checkboxes without `:checked` state

```css
/* Hide native; style label */
input[type="checkbox"] { position: absolute; opacity: 0; }
input[type="checkbox"] + label::before { content: "□"; }
input[type="checkbox"]:checked + label::before { content: "■"; }

/* WRONG — also need :focus visible */

/* RIGHT */
input[type="checkbox"]:focus + label::before {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}
```

### Anti-pattern 5: Honeypot fields without `aria-hidden`

Honeypot anti-spam fields MUST be `aria-hidden="true"` AND `tabindex="-1"` AND visually hidden —
screen-reader users would otherwise fill them and be flagged as bots.

### Anti-pattern 6: Auto-advancing OTP inputs without paste support

The 6-digit OTP UI with 6 separate inputs that auto-advance breaks copy-paste from SMS auto-fill.
Use a single `<input autocomplete="one-time-code" inputmode="numeric">` — iOS + Android auto-fill
works natively.

## Verification Checklist

- [ ] Every `<input>` has a `<label for>` OR is wrapped in `<label>`
- [ ] No `placeholder`-as-label patterns
- [ ] `autocomplete` tokens set on every applicable field (per WHATWG list)
- [ ] Required fields: `required` + `aria-required="true"` + visible indicator + form-level legend
  explaining the indicator
- [ ] Errors: `aria-invalid` + `aria-describedby` pointing to error text + `role="alert"` on the
  error element
- [ ] Error messages SUGGEST a fix (per WCAG 3.3.3)
- [ ] Tab order matches visual order; Shift+Tab works in reverse
- [ ] Submit button shows submitting state via `aria-busy`
- [ ] Multi-step forms: progress indicator with `aria-current="step"`; focus moves between steps
- [ ] No CAPTCHA without accessible alternative
- [ ] Password fields: paste enabled, `autocomplete="current-password"` / `"new-password"` set
- [ ] OTP fields: single input with `autocomplete="one-time-code"` + `inputmode="numeric"`
- [ ] File inputs: describe accepted formats + size limits via `aria-describedby`
- [ ] Date inputs: native `<input type="date">` OR custom picker implements APG keyboard model
- [ ] Tested with VoiceOver + NVDA + keyboard-only
- [ ] Tested with password manager (1Password, Bitwarden) — fills correctly
- [ ] Tested at 200% zoom and 320px viewport — no clipped fields
- [ ] axe DevTools form audit: 0 violations

## Cross-References

- `wcag-accessibility` skill — broader WCAG patterns
- `~/.claude/rules-library/common/a11y.md` — always-on rule
- `~/.claude/rules-library/common/i18n.md` — form field localisation (labels, errors, formats)
- `~/.claude/rules-library/common/error-codes.md` — error codes drive error messages
- `~/.claude/rules-library/common/error-handling-with-context.md` — server-side errors map to
  client-rendered messages
- `~/.claude/skills/frontend-patterns/SKILL.md` — broader component patterns
- `accessibility-reviewer` agent — opus-model audit

## Why This Skill Exists

Form a11y failures cause:

- Abandoned signups (conversion loss)
- Failed checkouts (revenue loss)
- Failed support requests (the user can't reach support because the contact form excludes them)
- Legal exposure (forms are the most-litigated a11y surface — they're concrete, demonstrable, and
  the harm is easy to prove)
- Brand damage (publicly shamed for inaccessible signup)

The patterns are mechanical: real labels, autocomplete tokens, ARIA states, validation timing,
accessible auth. The cost is one extra attribute per input + one extra div for errors. The benefit
is conversions that wouldn't have happened.

## Learning hooks

Per `~/.claude/rules/common/continuous-learning-mandate.md`:

**Signals to watch**:

- `placeholder` used as label (vanishes on focus — invisible to many users)
- `autocomplete` attribute missing on personal-info field (WCAG 1.3.5 + form fill UX)
- Error message colour-only (red border, no text — colour-only signal)
- Validation on every keystroke (premature error display — UX + a11y violation)
- Field error not associated via `aria-describedby` + `aria-invalid="true"`
- File-input without size + type constraint announced to screen reader
- Multi-step form without `aria-current="step"` + step indicator
- Required field marked only with `*` glyph (no text alternative)
- CAPTCHA without audio alternative / `prefers-reduced-motion`-friendly variant
- Re-entry of previously-entered info (WCAG 2.2 §3.3.7 — Redundant Entry)
- Password field without `autocomplete="current-password" | "new-password"`
- WCAG 2.2 §3.3.8 Accessible Authentication: cognitive function test without alternative

**Refinement candidates**:

- New input-pattern row when a new HTML autocomplete token / `inputmode` becomes useful
- New cross-reference when a sister skill (wcag-accessibility, interaction-design,
  frontend-patterns) adds a forms gate
- Tightening of the validation-timing rule when premature-validation incidents recur
- New row in the auth-accessibility checklist when new SSO / passkey UX emerges

Attribution

NmorNmor
View sourceMore from Nmor →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Ultra-compressed communication mode that cuts output tokens while keeping technical accuracy. Levels: lite, full, ultra and the wenyan variants. Use for /caveman, "caveman mode", "talk like caveman", "be brief" or "less tokens".

1074701 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

695601 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3351 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

691 votes

math-skill

A comprehensive mathematical reasoning skill for AI assistants — handles arithmetic to research-level problems with rigorous step-by-step reasoning, systematic verification, and transparent uncertainty handling

381 votes
View all in ai-agents →