> 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.md•accessibility
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 hardcodedpx - Do not add
user-scalable="no"ormaximum-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 oneh1per page, thenh2for sections,h3for sub-sections. Never skip levels - Parametric heading level: in reusable components, use
$headingTagwith 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 WordPresstitle-tag) lang="it"on<html>(managed bylanguage_attributes())- If a block is in another language: add
lang="en"on the container element
Forms
- Every field MUST have a label with
forpointing to the field'sid - Required fields:
requiredand/oraria-required="true" - Use
autocompletefor 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, usearia-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.): ALWAYSalt=""— they are decorative
Tables
- Use
<table>,<thead>,<th>,<tbody>,<td>for tabular data — NEVER for layout <th>withscope="col"orscope="row"<caption>oraria-labelledbyfor 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
tabindexvalues) - Only
tabindex="0"(tabbable) ortabindex="-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-haspopupwhere 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 descriptivetitleattribute
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
> related_skills --same-repo
> wp-sage
Conventions for WordPress Bedrock + Sage + Acorn + Blade + Tailwind CSS 4 + Vite projects. Always-active rules.
> wp-classic
Conventions for WordPress Bedrock projects with classic PHP theme (no Sage/Acorn/Blade). Always-active rules.
> wp-rest-api
WordPress REST endpoint development and debugging. Always-active rules when working with REST routes, API authentication, or data exposure via JSON.
> wp-plugin-development
WordPress plugin development — architecture, hooks, Settings API, security, data, and lifecycle. Always-active rules when working on plugins or mu-plugins.
┌ stats
installs/wk0
░░░░░░░░░░github stars9
██░░░░░░░░first seenMar 17, 2026
└────────────