Html Accessibility logo

Html Accessibility

Community
alpham8
html-accessibility

Write accessible HTML that works with screenreaders and keyboard navigation. Use when you need to: (1) build forms with proper labels, fieldsets, and error messages, (2) structure pages with semantic landmarks and heading hierarchy, (3) add alt text, ARIA attributes, or skip links, (4) audit HTML for accessibility violations, (5) make interactive components keyboard-accessible. Triggers on: HTML templates, Twig files, Vue/Svelte templates, form elements, ARIA attributes, "accessible", "screenreader", "keyboard navigation", "a11y", "WCAG", "barrierefreiheit". Minimum viable accessibility for every project -- no excuses.

Overview

Publisheralpham8
Repositoryagentic-webdev
Skill namehtml-accessibility
Stars
4
Forks
0
Bundled files
3
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 3 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by alpham8 on GitHub. Read the source before you install it.

Installation

Install the Html Accessibility AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/alpham8/agentic-webdev/tree/master/skills/wondelai/html-accessibility
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/alpham8/agentic-webdev.git /tmp/agentic-webdev
mkdir -p .claude/skills
cp -r /tmp/agentic-webdev/skills/wondelai/html-accessibility .claude/skills/html-accessibility
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Html Accessibility in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Html Accessibility on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Html Accessibility is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

HTML Accessibility

Practical, non-negotiable accessibility patterns for every web project. This skill focuses on what you must always do -- the minimum viable accessibility that ensures screenreaders can parse your HTML, keyboards can navigate it, and users with disabilities can use it.

This is not about achieving WCAG AAA perfection on every project. It's about never shipping inaccessible basics -- labels on inputs, alt on images, semantic structure, keyboard operability. These patterns cost almost nothing to implement but make a massive difference.

Core Principle

Use the right HTML element for the right job. Semantic HTML provides built-in accessibility for free. Every <div> that should be a <button>, every missing <label>, every decorative image without alt="" is a barrier you chose to create. The first rule of ARIA: don't use ARIA if native HTML can do it.

Scoring

Goal: 10/10. Rate HTML output 0-10 based on accessibility compliance.

  • 9-10: All inputs labelled, semantic structure correct, keyboard-navigable, images have appropriate alt, ARIA used only when necessary. Passes automated tools (axe, Lighthouse) with zero errors.
  • 7-8: Most patterns correct. Minor gaps: one missing alt, a decorative image with redundant alt text, one fieldset without legend.
  • 5-6: Basics present but inconsistent. Some labels missing, heading hierarchy broken, some interactive elements not keyboard-accessible.
  • 3-4: Significant gaps. Many inputs without labels, divs used as buttons, no landmark structure, images without alt.
  • 1-2: No accessibility consideration. Screenreaders cannot parse the page. Keyboard users cannot navigate.

The Non-Negotiable Patterns

1. Every Input Must Have a Label

The single most important accessibility rule for forms. Without a label, screenreaders announce "text edit" with no context -- users have no idea what to type.

html
<!-- ✅ ALWAYS: label with for/id association -->
<label for="email">E-Mail-Adresse</label>
<input type="email" id="email" name="email" autocomplete="email">

<!-- ✅ Also valid: wrapping label -->
<label>
    E-Mail-Adresse
    <input type="email" name="email" autocomplete="email">
</label>

<!-- ❌ NEVER: input without label -->
<input type="email" name="email" placeholder="E-Mail">

<!-- ❌ NEVER: placeholder as label substitute -->
<input type="email" placeholder="E-Mail-Adresse">

Rules:

  • Every <input>, <select>, and <textarea> needs an associated <label>
  • Use for="id" matching (preferred) or wrap the input inside the label
  • Placeholders are not labels -- they disappear on focus
  • Hidden labels (sr-only class) are acceptable when the visual design cannot show a label, but visible labels are always preferred
  • The <label> click target activates the input -- a usability bonus for all users

See: references/forms.md

2. Semantic Document Structure

Use landmarks and headings so screenreader users can navigate the page like a table of contents.

html
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <title>Descriptive Page Title</title>
</head>
<body>
    <a href="#main-content" class="sr-only sr-only--focusable">Zum Inhalt springen</a>

    <header>
        <nav aria-label="Hauptnavigation">
            <ul>
                <li><a href="/">Startseite</a></li>
                <li><a href="/produkte">Produkte</a></li>
            </ul>
        </nav>
    </header>

    <main id="main-content">
        <h1>Seitentitel</h1>

        <article>
            <h2>Abschnittstitel</h2>
            <p>Inhalt...</p>

            <h3>Unterabschnitt</h3>
            <p>Mehr Inhalt...</p>
        </article>

        <aside aria-label="Verwandte Inhalte">
            <h2>Ähnliche Artikel</h2>
        </aside>
    </main>

    <footer>
        <nav aria-label="Footer-Navigation">
            <ul>
                <li><a href="/impressum">Impressum</a></li>
                <li><a href="/datenschutz">Datenschutz</a></li>
            </ul>
        </nav>
    </footer>
</body>
</html>

Rules:

  • <html lang="de"> -- declare the page language (screenreaders switch pronunciation)
  • One <h1> per page; heading hierarchy never skips levels (h1 → h2 → h3)
  • Use landmarks: <header>, <nav>, <main>, <aside>, <footer>
  • <main> appears exactly once per page
  • Multiple <nav> elements need aria-label to distinguish them
  • <title> must be descriptive and unique per page
  • Add a skip link as the first focusable element

3. Images -- Alt Text or Explicit Decoration

Every <img> must have an alt attribute. The content depends on the image's purpose.

html
<!-- ✅ Informative image: describe what it shows -->
<img src="team-photo.jpg" alt="Das Entwicklerteam bei der Weihnachtsfeier 2024">

<!-- ✅ Functional image (link/button): describe the action -->
<a href="/warenkorb">
    <img src="cart-icon.svg" alt="Warenkorb (3 Artikel)">
</a>

<!-- ✅ Decorative image: empty alt to skip -->
<img src="decorative-border.png" alt="">

<!-- ✅ Complex image with extended description -->
<figure>
    <img src="chart.png" alt="Umsatzentwicklung 2024" aria-describedby="chart-desc">
    <figcaption id="chart-desc">
        Umsatz stieg von 1,2 Mio. Euro im Q1 auf 2,8 Mio. Euro im Q4,
        mit dem stärksten Wachstum im Q3 (+45%).
    </figcaption>
</figure>

<!-- ❌ NEVER: missing alt attribute -->
<img src="product.jpg">

<!-- ❌ NEVER: filename as alt -->
<img src="product.jpg" alt="product.jpg">

<!-- ❌ NEVER: redundant "Bild von..." -->
<img src="sunset.jpg" alt="Bild von einem Sonnenuntergang">

Rules:

  • Informative images: describe content and context concisely
  • Functional images (inside links/buttons): describe the action/destination
  • Decorative images: alt="" (empty, not omitted) -- screenreaders skip it
  • Never start alt with "Bild von" / "Image of" -- screenreaders already announce "graphic"
  • Never put text in images -- it can't be read by screenreaders or selected by users
  • SVG icons inside buttons: use aria-hidden="true" on the SVG and put text on the button

4. Buttons vs Links -- Use the Right Element

<a> navigates. <button> acts. Never use one for the other's job.

html
<!-- ✅ Link: navigates to a URL -->
<a href="/produkte">Produkte ansehen</a>

<!-- ✅ Button: performs an action -->
<button type="button" onclick="addToCart()">In den Warenkorb</button>

<!-- ✅ Submit button in a form -->
<button type="submit">Bestellung absenden</button>

<!-- ❌ NEVER: div/span as button -->
<div class="btn" onclick="doSomething()">Klick mich</div>

<!-- ❌ NEVER: link as button -->
<a href="#" onclick="doSomething(); return false;">Aktion ausführen</a>
<a href="javascript:void(0)" onclick="doSomething()">Aktion</a>

<!-- ❌ NEVER: button for navigation -->
<button onclick="window.location='/produkte'">Produkte</button>

Why it matters:

  • <button>: focusable, activates with Enter AND Space, announced as "button"
  • <a href>: focusable, activates with Enter only, announced as "link", can be opened in new tab
  • <div>: not focusable, no keyboard activation, no role announced -- invisible to assistive tech

If you absolutely must use a non-semantic element (legacy code, framework constraint):

html
<div role="button" tabindex="0" onkeydown="if(event.key==='Enter'||event.key===' ')doSomething()" onclick="doSomething()">
    Fallback-Button
</div>

But this is always worse than <button>. Prefer the real element.

5. Keyboard Navigation

Everything clickable must be keyboard-accessible. Users who can't use a mouse rely entirely on Tab, Enter, Space, Escape, and Arrow keys.

html
<!-- ✅ Naturally keyboard-accessible (no extra work needed) -->
<a href="/page">Link</a>
<button type="button">Button</button>
<input type="text">
<select><option>Option</option></select>

<!-- ✅ Custom interactive element made accessible -->
<div role="tab" tabindex="0" aria-selected="true" aria-controls="panel-1"
     onkeydown="handleTabKeydown(event)">
    Tab 1
</div>

<!-- ❌ Interactive but not keyboard-accessible -->
<div class="card" onclick="openDetail()">Product Card</div>

Rules:

  • Never use tabindex > 0 (reorders tab flow, causes confusion)
  • tabindex="0": adds element to natural tab order (use for custom widgets)
  • tabindex="-1": focusable via JavaScript only, not via Tab (use for focus management)
  • Never remove focus outlines without providing a visible alternative
  • Escape should close modals, dropdowns, and overlays
  • Focus must return to the trigger element when a modal/overlay closes
  • Test with keyboard only: can you reach and activate every interactive element?

See: references/keyboard-aria.md

6. Tables -- Structure for Screenreaders

html
<!-- ✅ Accessible table -->
<table>
    <caption>Bestellübersicht</caption>
    <thead>
        <tr>
            <th scope="col">Produkt</th>
            <th scope="col">Menge</th>
            <th scope="col">Preis</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Widget Pro</td>
            <td>2</td>
            <td>49,90 €</td>
        </tr>
    </tbody>
</table>

<!-- ❌ Inaccessible table -->
<table>
    <tr>
        <td><b>Produkt</b></td>
        <td><b>Menge</b></td>
        <td><b>Preis</b></td>
    </tr>
    <tr>
        <td>Widget Pro</td>
        <td>2</td>
        <td>49,90 €</td>
    </tr>
</table>

Rules:

  • Use <th> for header cells, not bold <td>
  • Add scope="col" or scope="row" to every <th>
  • Include <caption> for the table title
  • Use <thead>, <tbody>, <tfoot> for row grouping
  • Never use tables for layout -- use CSS Grid or Flexbox

7. Form Groups and Validation

html
<!-- ✅ Grouped radio buttons with legend -->
<fieldset>
    <legend>Zahlungsart</legend>
    <input type="radio" id="pay-card" name="payment" value="card">
    <label for="pay-card">Kreditkarte</label>

    <input type="radio" id="pay-paypal" name="payment" value="paypal">
    <label for="pay-paypal">PayPal</label>

    <input type="radio" id="pay-invoice" name="payment" value="invoice">
    <label for="pay-invoice">Rechnung</label>
</fieldset>

<!-- ✅ Error message associated with input -->
<div>
    <label for="plz">Postleitzahl</label>
    <input type="text" id="plz" name="plz" aria-describedby="plz-error" aria-invalid="true" inputmode="numeric" pattern="[0-9]{5}">
    <span id="plz-error" role="alert">Bitte geben Sie eine gültige 5-stellige PLZ ein.</span>
</div>

<!-- ✅ Required field -->
<label for="name">Name <span aria-hidden="true">*</span></label>
<input type="text" id="name" name="name" required aria-required="true">

Rules:

  • Group related radio buttons and checkboxes in <fieldset> with <legend>
  • Link error messages to inputs via aria-describedby
  • Mark invalid fields with aria-invalid="true"
  • Use role="alert" on dynamically appearing error messages (announces immediately)
  • Indicate required fields with required attribute AND visual indicator
  • Don't rely on colour alone for error states -- add icons or text

See: references/forms.md

8. Language and Text

html
<!-- ✅ Page language declared -->
<html lang="de">

<!-- ✅ Language switch for foreign phrases -->
<p>Das Konzept basiert auf dem <span lang="en">Design Thinking</span>-Ansatz.</p>

<!-- ✅ Abbreviation with expansion -->
<abbr title="Barrierefreie Informationstechnik-Verordnung">BITV</abbr>

<!-- ✅ Meaningful link text -->
<a href="/agb">Allgemeine Geschäftsbedingungen lesen</a>

<!-- ❌ Generic link text -->
<a href="/agb">hier klicken</a>
<a href="/agb">mehr</a>
<a href="/agb">Link</a>

Rules:

  • Declare lang on <html> and on inline elements in a different language
  • Never use "hier klicken", "mehr", or "Link" as link text -- describe the destination
  • Expand abbreviations with <abbr title="">on first use
  • Use clear, simple language; avoid jargon where possible

WCAG Quick Reference (Level A + AA)

The four POUR principles and the most critical success criteria:

Perceivable

CriterionRequirement
1.1.1 Non-text ContentAll images, icons, charts need text alternatives
1.3.1 Info and RelationshipsStructure (headings, lists, tables) must be programmatically determinable
1.3.2 Meaningful SequenceReading order in source must match visual order
1.4.1 Use of ColourNever convey information through colour alone
1.4.3 Contrast4.5:1 for text; 3:1 for large text (>= 24px or >= 18.8px bold)
1.4.4 Resize TextMust work at 200% zoom without loss of functionality

Operable

CriterionRequirement
2.1.1 KeyboardAll functionality available via keyboard
2.1.2 No Keyboard TrapFocus never gets trapped; user can always Tab away
2.4.1 Bypass BlocksSkip link or landmarks to bypass repeated content
2.4.3 Focus OrderTab order matches logical reading/visual order
2.4.4 Link PurposeLink text (or link + context) makes destination clear
2.4.7 Focus VisibleKeyboard focus indicator always visible
2.5.3 Label in NameVisible text label matches the accessible name

Understandable

CriterionRequirement
3.1.1 Language of Page<html lang="..."> declared
3.1.2 Language of PartsLanguage changes within the page marked with lang
3.2.1 On FocusNo unexpected context change on focus
3.3.1 Error IdentificationErrors identified in text, not just colour
3.3.2 Labels or InstructionsForm inputs have labels and instructions

Robust

CriterionRequirement
4.1.2 Name, Role, ValueCustom components expose name, role, state to assistive tech
4.1.3 Status MessagesDynamic messages use ARIA live regions

Common Mistakes

MistakeWhy It FailsFix
Input without labelScreenreader says "text edit" with no contextAdd <label for="id">
Placeholder as only labelDisappears on focus; not announced reliablyAdd visible label; keep placeholder as hint only
<div> as buttonNot focusable, no keyboard activation, no roleUse <button>
<a href="#"> as buttonWrong semantics, confuses assistive techUse <button> for actions
Missing alt on <img>Screenreader reads filenameAdd descriptive alt or alt="" for decorative
alt="image" or alt="icon"Useless informationDescribe what the image shows or does
Heading levels skipped (h1 → h3)Broken document outline; navigation confusedUse sequential levels
outline: none without replacementKeyboard users lose all orientationKeep outline or add custom :focus-visible style
Colour-only error indicationInvisible to colour-blind usersAdd icon + text + aria-invalid
No lang attributeScreenreader uses wrong pronunciationAdd lang="de" to <html>
Generic link text "hier klicken"Meaningless in link list contextDescribe the destination
Form without fieldset/legend for radio groupsScreenreader doesn't announce the group questionWrap in <fieldset> with <legend>

Quick Diagnostic

QuestionIf NoAction
Does every input have an associated label?Screenreaders can't identify fieldsAdd <label for="id"> to every input
Is there a skip link or landmark structure?Keyboard users must tab through entire navAdd <a href="#main"> skip link + <main> landmark
Does every <img> have an alt attribute?Screenreaders read filenamesAdd descriptive alt or alt=""
Can you Tab through all interactive elements?Keyboard users are blockedFix with semantic elements or tabindex="0"
Is there a visible focus indicator on every interactive element?Keyboard users are lostKeep default outline or add :focus-visible styles
Are headings in order (h1 → h2 → h3)?Document structure is brokenFix hierarchy; never skip levels
Do error messages use more than just colour?Colour-blind users miss errorsAdd icon + text + aria-invalid
Is <html lang="..."> set?Screenreaders mispronounce everythingAdd the correct language code

The sr-only Pattern

For visually hidden but screenreader-accessible content:

css
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Variant: visible on focus (for skip links) */
.sr-only--focusable:focus,
.sr-only--focusable:active {
    position: static;
    width: auto;
    height: auto;
    padding: inherit;
    margin: inherit;
    overflow: visible;
    clip: auto;
    white-space: inherit;
}

Use for: skip links, icon-only button labels, additional context for screenreaders. Never use display: none or visibility: hidden for screenreader content -- those hide from assistive tech too.

Reference Files

Further Reading

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Html Accessibility AI skill do?

Write accessible HTML that works with screenreaders and keyboard navigation. Use when you need to: (1) build forms with proper labels, fieldsets, and error messages, (2) structure pages with semantic landmarks and heading hierarchy, (3) add alt text, ARIA attributes, or skip links, (4) audit HTML for accessibility violations, (5) make interactive components keyboard-accessible. Triggers on: HTML templates, Twig files, Vue/Svelte templates, form elements, ARIA attributes, "accessible", "screenreader", "keyboard navigation", "a11y", "WCAG", "barrierefreiheit". Minimum viable accessibility for...

Why use Html Accessibility on TypingMind?

Because you install it once and use it with any model. Html Accessibility is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Html Accessibility in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/alpham8/agentic-webdev/tree/master/skills/wondelai/html-accessibility. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Html Accessibility?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Html Accessibility?

As many as you like. As long as a model supports skills, you can use Html Accessibility with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Html Accessibility AI skill free?

Yes. It is published on GitHub by alpham8 under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇