> accessibility

WCAG 2.2 checklist for accessible markup. Always-active rules for every generated interface.

fetch
$curl "https://skillshub.wtf/alessioarzenton/claude-code-wp-toolkit/accessibility?format=md"
SKILL.mdaccessibility

WCAG 2.2 Accessibility — Blade/Sage + Tailwind 4

Generated markup MUST be accessible. These rules apply always, even during prototyping.

Layout and units of measurement

  • Use relative units (rem, em, %) for spacing, text, and containers — NEVER hardcoded px
  • Do not add user-scalable="no" or maximum-scale="1" to the meta viewport
  • Layout must work from 320px to 1280px (ensured by mobile-first responsive design)
  • Support both portrait and landscape orientation

Semantic structure

  • Use headings (h1-h6) with logical hierarchy: only one h1 per page, then h2 for sections, h3 for sub-sections. Never skip levels
  • Parametric heading level: in reusable components, use $headingTag with a reasonable default for the context
  • Use <ul>/<ol> + <li> for lists, <blockquote> for quotes
  • Use semantic landmarks: <header>, <nav>, <main>, <aside>, <footer>, <section>, <article>
  • Label repeated landmarks with aria-label (e.g., <nav aria-label="Main menu">)
  • Every page has a descriptive <title> (managed by WordPress title-tag)
  • lang="it" on <html> (managed by language_attributes())
  • If a block is in another language: add lang="en" on the container element

Forms

  • Every field MUST have a label with for pointing to the field's id
  • Required fields: required and/or aria-required="true"
  • Use autocomplete for personal data (name, email, tel, street-address)
  • Group related fields with <fieldset> + <legend> (e.g., radio group, address)
  • Help text associated with aria-describedby
  • Error messages: associated with the field via aria-describedby, use aria-invalid="true" on the field

Blade field example:

<div class="{{PREFIX}}:flex {{PREFIX}}:flex-col {{PREFIX}}:gap-1">
  <label for="email" class="{{PREFIX}}-body-sm {{PREFIX}}:font-medium {{PREFIX}}-content-01">
    {{ __('Email', '{{TEXT_DOMAIN}}') }} <span class="{{PREFIX}}:text-error">*</span>
  </label>
  <input
    id="email"
    type="email"
    required
    autocomplete="email"
    aria-describedby="email-help"
    class="{{PREFIX}}-input"
  />
  <p id="email-help" class="{{PREFIX}}-body-xs {{PREFIX}}-content-03">
    {{ __('Useremo questa email per risponderti.', '{{TEXT_DOMAIN}}') }}
  </p>
</div>

Field with error example:

<input
  id="email"
  type="email"
  required
  aria-invalid="true"
  aria-describedby="email-error"
  class="{{PREFIX}}-input {{PREFIX}}-border-error"
/>
<p id="email-error" role="alert" class="{{PREFIX}}-body-xs {{PREFIX}}:text-error">
  {{ __('Inserisci un indirizzo email valido.', '{{TEXT_DOMAIN}}') }}
</p>

Images and icons

  • Informative images: <img alt="Meaningful description">
  • Decorative images: <img alt=""> (empty alt, not omitted)
  • Decorative SVGs (icons in buttons with text): aria-hidden="true" focusable="false"
  • Informative SVGs (standalone icons): role="img" aria-label="Description"
  • Icon fonts: aria-hidden="true" on the icon element
  • Visual separators (vertical-divider, etc.): ALWAYS alt="" — they are decorative

Tables

  • Use <table>, <thead>, <th>, <tbody>, <td> for tabular data — NEVER for layout
  • <th> with scope="col" or scope="row"
  • <caption> or aria-labelledby for the caption (can be hidden with {{PREFIX}}:sr-only)

Keyboard and focus

  • NEVER remove the focus outline without a visible alternative
  • Focus ring: use --color-focusring (#025ecc)
  • Use native elements: <button>, <a>, <input>, <select> — NEVER <div onclick>
  • Tab order must follow the visual order (do not use positive tabindex values)
  • Only tabindex="0" (tabbable) or tabindex="-1" (focusable via JS)
  • Overlays (modal, dropdown, tooltip): dismissible with Escape
  • Focus trap only in modal dialogs
  • Component with focus must be at least partially visible
  • For interaction patterns: follow ARIA APG

Links

  • Never <a role="button"> — use <a> for navigation, <button> for actions
  • If target="_blank": add <span class="{{PREFIX}}:sr-only">{{ __('(si apre in una nuova finestra)', '{{TEXT_DOMAIN}}') }}</span>
  • Active links/current page: use aria-current="page" (not just CSS classes)

Dynamic content

  • Tooltips and popovers: dismissible with Escape, hoverable, persistent
  • Status messages (success, loading): role="status"
  • Urgent alerts (errors, alerts): role="alert"
  • Interactive components: aria-expanded, aria-controls, aria-haspopup where appropriate

Colors and contrast

  • Do not convey information through color alone — always add a textual or iconographic indicator
  • Verify minimum contrast of 4.5:1 for normal text, 3:1 for large text

Page navigation

  • Skip link present in layout (app.blade.php — implemented)
  • <iframe>: always with a descriptive title attribute

Summary rule

  • Interactive -> focusable, labeled, keyboard-operable
  • Informative -> described with alternative text or label
  • Decorative -> hidden from assistive technologies (aria-hidden="true")

References

  • Full checklist: a11y-checklist.md (in this skill's directory)
  • Known project issues: docs/a11y-known-issues.md

┌ stats

installs/wk0
░░░░░░░░░░
github stars9
██░░░░░░░░
first seenMar 17, 2026
└────────────

┌ repo

alessioarzenton/claude-code-wp-toolkit
by alessioarzenton
└────────────

┌ tags

└────────────