From rad-a11y
Use this skill when the user asks about semantic HTML structure, heading hierarchy, landmark regions, div soup, divitis, "which HTML element should I use", "semantic markup", "HTML accessibility", "ARIA landmarks", "page structure", "section vs div", "article vs section", "when to use aside", or is writing HTML templates, page layouts, or component markup that requires accessible structural decisions. Also use when reviewing HTML for screen reader compatibility or document outline correctness.
How this skill is triggered — by the user, by Claude, or both
Slash command
/rad-a11y:a11y-semantic-htmlThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> **Skill type: Reference.** This is teaching/pattern content — it explains *which element to use when* and *why*. It is not a scanner. For static review of existing markup, use `/a11y-review`. For runtime verification, use the `a11y-testing` skill to set up real axe.
Skill type: Reference. This is teaching/pattern content — it explains which element to use when and why. It is not a scanner. For static review of existing markup, use
/a11y-review. For runtime verification, use thea11y-testingskill to set up real axe.
Semantic HTML is the foundation of accessible web content. The right element provides built-in roles, keyboard behavior, and accessibility tree information that ARIA cannot fully replicate.
Core principle: Always use the most semantically specific native HTML element available. ARIA is a supplement for custom widgets — not a replacement for correct HTML.
<h1>, Sequential Order, No SkipsHeadings create a machine-readable outline that screen reader users navigate by jumping between levels.
<!-- CORRECT: Sequential, logical hierarchy -->
<h1>Guide to Accessible Design</h1>
<h2>Color and Contrast</h2>
<h3>Text Contrast Requirements</h3>
<h3>UI Component Contrast</h3>
<h2>Keyboard Navigation</h2>
<h3>Focus Management</h3>
<!-- WRONG: Skipped level — screen reader users think they missed content -->
<h1>Guide to Accessible Design</h1>
<h3>Color and Contrast</h3> <!-- ❌ jumped from h1 to h3 -->
Rules:
<h1> per page — the primary descriptor of the page's topich1 → h3 skips h2)h3 → h2 when starting a new section)font-size. Heading level = document structure, not visual hierarchy.// WRONG: Using h4 because you want small text
<h4 className="text-sm font-medium text-gray-500">Card Label</h4>
// CORRECT: Use the right level, control size with CSS
<h3 className="text-sm font-medium text-gray-500">Card Label</h3>
// CORRECT: If it's not actually a heading, use a <p> or <span>
<p className="text-sm font-medium text-gray-500 uppercase tracking-wide">Card Label</p>
HTML5 landmark elements map directly to ARIA landmark roles. Screen reader users navigate between landmarks to skip to the content they want.
| Element | Implicit ARIA Role | Use For |
|---|---|---|
<header> | banner | Site header, logo, primary navigation |
<nav> | navigation | Navigation menus, breadcrumbs |
<main> | main | The page's unique, primary content |
<footer> | contentinfo | Site footer, copyright, secondary nav |
<aside> | complementary | Sidebars, related content, pull quotes |
<section> | region (only with a label) | Thematic groupings with a heading |
<article> | article | Self-contained, repeatable content units |
Every page must have exactly one <main>:
<!-- CORRECT: One main, clearly the primary content -->
<body>
<header>...</header>
<nav aria-label="Primary">...</nav>
<main id="main-content">
<h1>Page Title</h1>
<!-- unique page content here -->
</main>
<footer>...</footer>
</body>
Multiple landmarks of the same type require unique labels:
<!-- WRONG: Two nav elements — screen reader can't tell them apart -->
<nav>Primary menu</nav>
<nav>Footer menu</nav>
<!-- CORRECT: Labeled uniquely -->
<nav aria-label="Primary">...</nav>
<nav aria-label="Footer">...</nav>
<section> only creates a landmark region if it has a label:
<!-- Creates no landmark (section without label) -->
<section>...</section>
<!-- Creates a named region landmark -->
<section aria-labelledby="trending-heading">
<h2 id="trending-heading">Trending Articles</h2>
...
</section>
Before reaching for <div>, ask:
<nav><main><article><section aria-labelledby="..."><aside><ul> / <ol> + <li><button> (never <div onClick>)<a href="..."> (never <div onClick>)<table> + <thead> + <tbody> + <th scope> + <td><div> is appropriate<!-- WRONG: Zero keyboard accessibility, no announced role -->
<div class="btn" onclick="submitForm()">Submit</div>
<!-- CORRECT: Native button — keyboard focusable, Enter/Space activatable -->
<button type="button" onclick="submitForm()">Submit</button>
If you absolutely must use a non-button element as a button:
<div
role="button"
tabindex="0"
onclick="submitForm()"
onkeydown="if(e.key==='Enter'||e.key===' ')submitForm()">
Submit
</div>
This requires: role="button", tabindex="0", AND keyboard event handlers. All three. Native <button> is always better.
<!-- WRONG: Visual list with no semantic meaning -->
<div class="nav-links">
<div>Home</div>
<div>About</div>
<div>Contact</div>
</div>
<!-- CORRECT: Screen readers announce "list, 3 items" -->
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
Tailwind note: Applying list-none to a <ul> removes list semantics in some screen readers (VoiceOver on Safari). If list semantics matter, add role="list" explicitly:
<ul class="list-none space-y-2" role="list">...</ul>
| Element | Use When | Keyboard |
|---|---|---|
<button> | Performs an action (submit, toggle, open modal) | Tab to focus, Enter or Space to activate |
<a href> | Navigates to a URL | Tab to focus, Enter to activate |
<!-- CORRECT -->
<button type="button" onclick="openModal()">Open Settings</button>
<a href="/settings">Go to Settings</a>
<!-- WRONG: Link used as a button (no href, or href="#") -->
<a href="#" onclick="openModal()">Open Settings</a>
<!-- ❌ Pressing Space on this scrolls the page instead of activating -->
Every interactive form control needs an associated label:
<!-- Method 1: Explicit association (preferred) -->
<label for="email">Email Address</label>
<input type="email" id="email" name="email">
<!-- Method 2: Wrapping label -->
<label>
Email Address
<input type="email" name="email">
</label>
<!-- WRONG: Input without any label -->
<input type="email" placeholder="Enter your email">
<!-- ❌ Placeholder disappears on typing; fails WCAG 3.3.2 -->
Use <table> for tabular data only — never for layout. Data tables need proper header markup:
<table>
<caption>Monthly Sales by Region</caption>
<thead>
<tr>
<th scope="col">Month</th>
<th scope="col">North</th>
<th scope="col">South</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">January</th>
<td>$10,000</td>
<td>$8,500</td>
</tr>
</tbody>
</table>
<caption>: describes the table (equivalent to alt text for images)scope="col" on column headersscope="row" on row headersid + headers attribute// WRONG: Wrapper div breaks ul > li structure
function NavItem({ href, label }) {
return (
<div> {/* ❌ <ul> → <div> → <li> is invalid HTML */}
<li><a href={href}>{label}</a></li>
</div>
);
}
// CORRECT: Fragment preserves ul > li
function NavItem({ href, label }) {
return (
<>
<li><a href={href}>{label}</a></li>
</>
);
}
<section> vs. <article> vs. <div>| Element | Use When |
|---|---|
<article> | Content could be syndicated standalone (blog posts, news articles, comments, product cards, tweets) |
<section> | Thematic grouping within the page that deserves its own heading |
<div> | No semantic meaning needed — purely structural/layout |
<!-- Blog page structure -->
<main>
<h1>Our Blog</h1>
<section aria-labelledby="featured-heading">
<h2 id="featured-heading">Featured Posts</h2>
<article>
<h3><a href="/post-1">Post Title</a></h3>
<p>Post excerpt...</p>
</article>
</section>
<section aria-labelledby="recent-heading">
<h2 id="recent-heading">Recent Posts</h2>
<!-- more articles -->
</section>
</main>
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
npx claudepluginhub radorigin-llc/rad-claude-skills --plugin rad-a11y