agentsclimarketplace

A11y semantic html

Skill RadOrigin-LLC/RAD-Claude-Skills/plugins/rad-a11y/skills/a11y-semantic-html

Marketplace of plugins and skills for Claude Code

Install
npx -y skills add RadOrigin-LLC/RAD-Claude-Skills --skill a11y-semantic-html

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

9.2 KB, as published. Nobody here has run it

Semantic HTML for Accessibility

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.

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.


Document Outline and Headings

The Heading Rule: One <h1>, Sequential Order, No Skips

Headings 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:

  • One <h1> per page — the primary descriptor of the page's topic
  • Never skip heading levels downward (h1h3 skips h2)
  • You CAN jump back up (h3h2 when starting a new section)
  • Never use headings for visual size. Use CSS font-size. Heading level = document structure, not visual hierarchy.

Heading Level vs. Visual Appearance (Tailwind/React pattern)

// 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>

Landmark Regions

HTML5 landmark elements map directly to ARIA landmark roles. Screen reader users navigate between landmarks to skip to the content they want.

ElementImplicit ARIA RoleUse For
<header>bannerSite header, logo, primary navigation
<nav>navigationNavigation menus, breadcrumbs
<main>mainThe page's unique, primary content
<footer>contentinfoSite footer, copyright, secondary nav
<aside>complementarySidebars, related content, pull quotes
<section>region (only with a label)Thematic groupings with a heading
<article>articleSelf-contained, repeatable content units

Landmark Rules

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>

Semantic Elements vs. Generic Divs

The "Right Element" Decision Tree

Before reaching for <div>, ask:

  1. Is this a navigation menu?<nav>
  2. Is this the primary page content?<main>
  3. Is this a standalone, shareable content unit (blog post, product card, comment)?<article>
  4. Is this a thematic section with its own heading?<section aria-labelledby="...">
  5. Is this sidebar or complementary content?<aside>
  6. Is this a list of items?<ul> / <ol> + <li>
  7. Is this a button that triggers an action?<button> (never <div onClick>)
  8. Is this a link to another page/URL?<a href="..."> (never <div onClick>)
  9. Is this a data table?<table> + <thead> + <tbody> + <th scope> + <td>
  10. Is this purely a layout container?<div> is appropriate

Div-as-Button Anti-Pattern

<!-- 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.

Lists and List Items

<!-- 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>

Interactive Elements

Buttons vs. Links

ElementUse WhenKeyboard
<button>Performs an action (submit, toggle, open modal)Tab to focus, Enter or Space to activate
<a href>Navigates to a URLTab 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 -->

Form Inputs

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 -->

Tables

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 headers
  • scope="row" on row headers
  • For complex tables: use id + headers attribute

React-Specific: Fragments Prevent Semantic Breaks

// 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>

ElementUse 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>

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.