本文へ移動
cccskills
無料GitHub で公開

angular-accessibility

Audit and fix common accessibility issues in Angular templates and Angular Material components. Use when the user mentions Lighthouse, axe, screen readers, keyboard navigation, ARIA, asks to fix a11y issues in Angular HTML templates, or after any feature, bug fix, or refactor that changed Angular templates.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md10.7 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Angular Accessibility (a11y)

When To Activate

  • After any development touching Angular templates, including a new feature, bug fix, or refactor
  • After updating Angular Material markup, control structure, labels, dialogs, tables, forms, or navigation UI
  • Fixing Lighthouse, axe, or ESLint accessibility findings
  • Auditing Angular templates or Angular Material screens
  • Debugging keyboard navigation, focus order, or screen reader output
  • Refactoring interactive UI in Angular HTML templates

Default Post-Development Trigger

Run this skill as a final verification step whenever the implementation modified .html templates or materially changed rendered UI structure, even if the original task did not mention accessibility.

Typical triggers:

  • New feature that introduces or changes template markup
  • Bug fix that changes form controls, buttons, tables, dialogs, drawers, or navigation
  • Refactor that restructures conditionals, loops, landmarks, headings, or interactive elements
  • Angular Material component replacement or reconfiguration affecting labels, focus, or keyboard behavior

Hard Rules

  • Prefer semantic HTML first: native <button>, <a>, <label>, <main>, <table>, and <th> before ARIA workarounds
  • Prefer visible labels, <label for>, <mat-label>, or aria-labelledby before aria-label
  • Use ARIA to fill missing semantics, not to replace native semantics
  • For native elements, bind ARIA with [attr.aria-*]
  • For Angular Material components that expose dedicated inputs, prefer [aria-label] or [aria-labelledby] without attr.
  • Do not add redundant roles to native controls (role="button" on <button>, role="checkbox" on <input type="checkbox">, etc.)
  • All user-facing accessible text must be translated with Transloco. Never hard-code text in templates
  • After each fix, verify keyboard and focus behavior, not just lint output

Audit Workflow

  1. Run the ESLint accessibility check first:
    npx eslint "src/app/<feature>/**/*.html"
    
  2. Run an automated audit on the affected screen:
    • Lighthouse
    • axe DevTools or equivalent
  3. Perform a keyboard-only smoke test:
    • Tab / Shift+Tab reaches all interactive controls in a logical order
    • Enter / Space activates buttons, toggles, and checkboxes
    • Escape closes dialogs, menus, or drawers where applicable
    • Focus remains visible throughout the flow
  4. Spot-check the changed flow with a screen reader
  5. Fix issues systematically using the patterns below
  6. Re-run lint and the automated audit on the affected screen

Common Issues & Fixes

1. Icon-only buttons - missing accessible name

Any <button> whose only content is a <mat-icon> or similar icon has no accessible name.

Fix: add a translated accessible name and mark the icon as decorative.

<!-- ❌ Before -->
<button type="button" (click)="onClose()">
  <mat-icon>close</mat-icon>
</button>

<!-- ✅ After -->
<button
  type="button"
  [attr.aria-label]="t('scope.key.close-btn-aria')"
  (click)="onClose()"
>
  <mat-icon aria-hidden="true">close</mat-icon>
</button>

Same pattern applies to pagination, toggle, toolbar, and other icon-only actions.


2. Form controls - missing programmatic label

Prefer a visible label first. Use aria-label only when a visible label or aria-labelledby is not practical.

Standard forms: use <label for> or <mat-label>.

<!-- ✅ Preferred in a regular form -->
<mat-form-field>
  <mat-label>{{ t('scope.key.amount') }}</mat-label>
  <input matInput id="amount" type="number" />
</mat-form-field>

Dense table/grid cells: use row-level context so each control is uniquely identifiable.

<!-- ❌ Before -->
<input matInput type="number" [value]="row.amount" />
<mat-select [value]="row.type"></mat-select>

<!-- ✅ After -->
<input
  matInput
  type="number"
  [value]="row.amount"
  [attr.aria-label]="t('scope.key.amount-aria', { invoiceNumber: row.invoiceNumber })"
/>

<mat-select
  [value]="row.type"
  [aria-label]="t('scope.key.type-aria', { invoiceNumber: row.invoiceNumber })"
>
</mat-select>

3. Checkboxes without labels

Empty mat-checkbox components with no projected text need an accessible name.

<!-- ❌ Before -->
<mat-checkbox
  [checked]="allSelected()"
  (change)="onToggleAll($event.checked)"
/>

<!-- ✅ After - header (select-all) -->
<mat-checkbox
  [aria-label]="t('scope.key.select-all-aria')"
  [checked]="allSelected()"
  (change)="onToggleAll($event.checked)"
/>

<!-- ✅ After - row (select one) -->
<mat-checkbox
  [aria-label]="t('scope.key.select-row-aria', { invoiceNumber: row.invoiceNumber })"
  [checked]="row.selected"
  (change)="onToggleItem(row.id, $event.checked)"
/>

4. Non-semantic interactive elements

Clickable <div> and <span> elements break keyboard and assistive technology behavior.

Fix: replace them with native interactive elements whenever possible.

<!-- ❌ Before -->
<div class="close-action" (click)="onClose()">
  <mat-icon>close</mat-icon>
</div>

<!-- ✅ After -->
<button
  type="button"
  class="close-action"
  [attr.aria-label]="t('scope.key.close-btn-aria')"
  (click)="onClose()"
>
  <mat-icon aria-hidden="true">close</mat-icon>
</button>

Use <a> for navigation and <button> for in-page actions.


5. Viewport blocks zoom

maximum-scale or user-scalable=no in the viewport meta tag prevents low-vision users from magnifying the page.

Fix (src/index.html): keep only width=device-width, initial-scale=1.0.

<!-- ❌ Before -->
<meta
  name="viewport"
  content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>

<!-- ✅ After -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

6. Missing <main> landmark

Screen readers use landmarks to jump between page regions. A page must have exactly one <main> element.

In this project the layout can render two conditional branches (mobile / desktop). Each branch should expose its content wrapper as <main>. Since they are mutually exclusive, only one <main> is in the DOM at a time.

<!-- mobile: main.component.html -->
<!-- ❌ Before: <div class="main-container"> -->
<main class="main-container" cdkVirtualScrollingElement>
  <gc-sub-header />
  <router-outlet />
</main>

<!-- desktop: page.component.html -->
<!-- ❌ Before: <div class="desktop-page-content"> -->
<main class="desktop-page-content">
  <router-outlet />
</main>

SCSS that targets .main-container or .desktop-page-content keeps working when only the element tag changes.


7. Spinners and loading states

mat-progress-spinner already follows the ARIA progressbar pattern. Do not replace it with role="status".

Fix:

  • keep the component's default semantics
  • add an accessible name if the spinner would otherwise be ambiguous
  • use a nearby aria-live="polite" message only when the loading state itself must be announced
<!-- ✅ Spinner keeps its default progressbar semantics -->
<mat-progress-spinner
  [attr.aria-label]="t('shared.loading-aria')"
  mode="indeterminate"
  diameter="32"
/>

<!-- ✅ Optional live region when the loading state should be announced -->
@if (isLoading()) {
<span aria-live="polite">{{ t('shared.loading-aria') }}</span>
}

8. Dialogs, drawers, and disclosure controls

Interactive overlays and expandable sections must expose state and preserve focus behavior.

Verify:

  • the trigger has an accessible name
  • expandable controls expose aria-expanded when they show or hide related content
  • dialogs have a visible title and, when needed, a description
  • focus moves into the dialog when opened and returns to the trigger when closed
<button
  type="button"
  aria-controls="filters-panel"
  [attr.aria-expanded]="isFiltersOpen()"
  (click)="toggleFilters()"
>
  {{ t('scope.key.filters') }}
</button>

@if (isFiltersOpen()) {
<section id="filters-panel">...</section>
}

For Angular Material dialogs, keep the built-in focus management unless there is a concrete need to change it.


Translation Convention

All accessible names and descriptions must use translated strings via Transloco. Never hard-code French or any other language directly in templates.

  • In templates, follow the existing t('scope.key') pattern
  • Key naming: append -aria to accessible-name keys and -description when the text is used for extra context
  • Add keys to the feature's scoped fr-FR.json
  • Use contextual params when the label must uniquely identify a row or control

Example JSON additions:

{
  "table": {
    "select-all-aria": "Sélectionner toutes les échéances",
    "select-row-aria": "Sélectionner l'échéance {{invoiceNumber}}",
    "discount-amount-aria": "Montant escompte pour la facture {{invoiceNumber}}"
  },
  "pagination": {
    "previous-aria": "Page précédente",
    "next-aria": "Page suivante"
  },
  "selection-detail": {
    "close-btn-aria": "Fermer le volet de détails"
  }
}

Validation Checklist

After fixing, verify:

  • Viewport meta tag has no maximum-scale or user-scalable=no
  • Page has exactly one <main> landmark
  • All icon-only buttons have an accessible name
  • Decorative icons inside labelled controls use aria-hidden="true"
  • Form controls have a visible label, aria-labelledby, or aria-label as appropriate
  • Empty mat-checkbox components have [aria-label] or [aria-labelledby]
  • Clickable div and span elements were replaced with semantic controls
  • Expand/collapse controls expose aria-expanded when applicable
  • Dialogs and drawers keep correct focus entry and focus return behavior
  • Loading indicators keep correct semantics and use a live region only when announcement is needed
  • Informative images have meaningful alt; decorative images use empty alt=""
  • Keyboard-only navigation works end-to-end and focus stays visible
  • All accessible text uses translated strings
  • ESLint passes: npx eslint "src/app/<feature>/**/*.html"
  • Automated audit was re-run on the affected screen

Known Limits

  • This skill covers common template and component-level accessibility issues, not full WCAG certification
  • Color contrast, timing, motion, content wording, and design-system token changes may require separate design or product decisions
  • Complex screen reader behavior should be validated on the real user flow, not assumed from lint output alone

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Load this skill immediately whenever a project contains an ACCESSIBILITY.md file, or whenever you are contributing to the mgifford/ACCESSIBILITY.md repository. Absolutely always read ACCESSIBILITY.md before proposing or writing any changes. Under no circumstances skip this skill when an ACCESSIBILITY.md file is present. This skill governs when and how all other accessibility topic skills must be loaded.

日本語の概要は準備中です。原文の説明を表示しています。

LazyCats-dev/ao3-podfic-posting-helper112026年10月5日 更新

Load this skill whenever the project contains in-page anchor links, skip links, fragment identifiers, or heading links. Under no circumstances omit accessible anchor-link requirements when such links exist. Ensure every anchor link has meaningful text, a reachable target, and a visible focus indicator. Apply these rules when creating or reviewing any in-page navigation.

日本語の概要は準備中です。原文の説明を表示しています。

LazyCats-dev/ao3-podfic-posting-helper112026年10月5日 更新

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

日本語の概要は準備中です。原文の説明を表示しています。

LazyCats-dev/ao3-podfic-posting-helper112026年10月5日 更新

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, services, or HTTP communication, or for best practices on reactivity (signals, linkedSignal, resource, httpResource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, naming conventions, or CLI tooling.

日本語の概要は準備中です。原文の説明を表示しています。

LazyCats-dev/ao3-podfic-posting-helper112026年10月5日 更新

Creates a new Angular app using the Angular CLI. This skill should be used whenever a user wants to create a new Angular application and contains important guidelines for how to effectively create a modern Angular application.

日本語の概要は準備中です。原文の説明を表示しています。

LazyCats-dev/ao3-podfic-posting-helper112026年10月5日 更新

Load this skill whenever the project contains dynamic content updates, status messages, alerts, notifications, loading indicators, or any use of aria-live, role="status", role="alert", or role="log". Under no circumstances add or modify live-region markup without applying these rules. Prioritize correct politeness levels and avoid redundant announcements. Never use a fixed-delay clear-and-reinsert workaround — no standard guarantees it works.

日本語の概要は準備中です。原文の説明を表示しています。

LazyCats-dev/ao3-podfic-posting-helper112026年10月5日 更新

LazyCats-dev のスキルをすべて見る

このスキルの問題を報告する