WCAG 2.1 AA compliance requirements including semantic markup, keyboard navigation, ARIA labels, and color contrast
This skill ensures all web content meets WCAG 2.1 Level AA standards for accessibility, making websites usable by people with disabilities including visual, auditory, motor, and cognitive impairments.
WCAG is organized around four principles (POUR):
MUST:
alt text for all meaningful imagesalt="" for decorative imagesExamples:
<!-- GOOD: Meaningful alt text -->
<img src="security-audit.jpg" alt="Security professional reviewing network logs on dual monitors">
<!-- GOOD: Decorative image -->
<img src="divider.png" alt="" aria-hidden="true">
<!-- BAD: Missing alt -->
<img src="chart.png">
<!-- BAD: Useless alt text -->
<img src="photo.jpg" alt="Image">
MUST:
MUST:
Examples:
<!-- GOOD: Semantic markup -->
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<!-- BAD: Div soup -->
<div class="nav">
<div class="item"><a href="/">Home</a></div>
<div class="item"><a href="/about">About</a></div>
</div>
MUST:
Color Contrast Examples:
/* GOOD: High contrast (7:1) */
.text {
color: #000000; /* Black */
background-color: #FFFFFF; /* White */
}
/* GOOD: Sufficient contrast (4.6:1) */
.link {
color: #0066CC; /* Blue */
background-color: #FFFFFF; /* White */
}
/* BAD: Insufficient contrast (2.3:1) */
.subtle-text {
color: #CCCCCC; /* Light gray */
background-color: #FFFFFF; /* White */
}
MUST:
Examples:
<!-- GOOD: Button is keyboard accessible -->
<button onclick="submitForm()">Submit</button>
<!-- BAD: Div onClick is not keyboard accessible -->
<div onclick="submitForm()">Submit</div>
<!-- BETTER: If you must use div, add role and tabindex -->
<div role="button" tabindex="0" onclick="submitForm()" onkeypress="handleKeyPress(event)">
Submit
</div>
/* GOOD: Visible focus indicator */
a:focus,
button:focus {
outline: 2px solid #0066CC;
outline-offset: 2px;
}
/* BAD: Removing focus outline without replacement */
*:focus {
outline: none; /* Never do this without providing alternative! */
}
MUST:
MUST NOT:
MUST:
Examples:
<!-- GOOD: Skip link -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav>...</nav>
<main id="main-content">...</main>
<!-- GOOD: Descriptive title -->
<title>Security Services - ISO 27001 Implementation | Hack23</title>
<!-- BAD: Generic title -->
<title>Services</title>
MUST:
MUST:
lang attributeExamples:
<!-- GOOD: Language specified -->
<html lang="en">
<!-- GOOD: Foreign language phrase -->
<p>The phrase <span lang="fr">je ne sais quoi</span> is French.</p>
MUST:
MUST:
Examples:
<!-- GOOD: Clear label and error -->
<label for="email">Email Address *</label>
<input type="email" id="email" aria-required="true" aria-describedby="email-error">
<span id="email-error" class="error" role="alert">
Please enter a valid email address (e.g., name@example.com)
</span>
<!-- GOOD: Required field indicator -->
<label for="password">
Password *
<span class="required-note">(required)</span>
</label>
<input type="password" id="password" aria-required="true">
MUST:
ARIA Examples:
<!-- GOOD: ARIA for custom component -->
<div role="tablist" aria-label="Account settings">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
Profile
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
Security
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<!-- Profile content -->
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
<!-- Security content -->
</div>
<!-- GOOD: Status message -->
<div role="status" aria-live="polite" aria-atomic="true">
Changes saved successfully
</div>
Use ARIA When:
ARIA Landmarks:
<header role="banner">
<nav role="navigation" aria-label="Main navigation">
<main role="main">
<aside role="complementary">
<footer role="contentinfo">
ARIA States and Properties:
aria-label - Provides accessible namearia-labelledby - References element that labels this onearia-describedby - References element that describes this onearia-hidden - Hides element from screen readersaria-live - Announces dynamic content changesaria-expanded - Indicates if expandable element is openaria-pressed - Indicates toggle button statearia-current - Indicates current item in setMUST TEST:
Keyboard Navigation
Screen Reader
Color Contrast
Zoom/Resize
Automated Tools
<!-- BAD -->
<img src="logo.png">
<!-- GOOD -->
<img src="logo.png" alt="Hack23 Cybersecurity">
/* BAD: 2.5:1 ratio */
color: #999999;
background: #FFFFFF;
/* GOOD: 7:1 ratio */
color: #333333;
background: #FFFFFF;
<!-- BAD -->
<input type="text" placeholder="Email">
<!-- GOOD -->
<label for="email">Email</label>
<input type="text" id="email">
<!-- BAD -->
<a href="report.pdf">
<img src="pdf-icon.png">
</a>
<!-- GOOD -->
<a href="report.pdf">
<img src="pdf-icon.png" alt="Download Annual Report (PDF, 2MB)">
</a>
<!-- BAD -->
<a href="/services">Click here</a> for our services.
<!-- GOOD -->
<a href="/services">View our cybersecurity services</a>
Every website MUST include an accessibility statement at /accessibility-statement.html that includes:
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer