npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

mat-a11y

v7.0.0

Published

Angular Material accessibility linter (v12+). 82 WCAG checks for mat-* components, Angular templates & SCSS. Static analysis with color contrast calculation.

Readme

mat-a11y

npm version license node

Accessibility linter for Angular Material. 82 checks. AI-optimized output. Battle-tested on traufix.de with 300+ components.

Try the Live Demo

Launch Interactive Demo — No installation required. See real scan results from a production Angular app with 300+ components.


Quick Start

npx mat-a11y

Opens an interactive GUI dashboard in your browser — designed for accessibility testers and management, not just developers.

Features:

  • Dark mode support
  • 18 export formats (HTML, PDF, JSON, SARIF, JUnit, Slack, Teams, and more)
  • Syntax-highlighted preview for all formats
  • Fuzzy search across all export options
  • Expert mode with detailed statistics

Headless mode (for scripts/CI):

  • npx mat-a11y --headless_mat-a11y.backlog.txtAI backlog sample
  • npx mat-a11y --headless --html_mat-a11y.htmlHTML sample
  • npx mat-a11y --headless --json_mat-a11y.jsonJSON sample

The Problem

Accessibility is important, but fixing it is expensive. Manual audits, expert consultants, endless backlogs. Most teams know they should do better, but ship inaccessible apps because the cost is too high.

We built mat-a11y to change that.

The workflow:

  1. mat-a11y finds every accessibility issue in your codebase (static analysis)
  2. mat-a11y generates a structured TODO list optimized for AI
  3. Your AI fixes the issues (we validated with Claude Opus 4.5)

Why mat-a11y?

Traditional tools are too slow for AI workflows.

Tools like Lighthouse, axe, and WAVE run in the browser against compiled, rendered HTML. That means: build your app, launch a browser, navigate to each page, wait for rendering, run the audit. For a 50-page app, that's 10+ minutes just to get a list of issues.

In the age of AI, that's unacceptable. When Claude can fix 100 issues in 30 seconds, you can't spend 10 minutes waiting for the audit.

mat-a11y analyzes your source code directly — no build, no browser, no waiting:

| | Traditional (Lighthouse) | mat-a11y | |--|-------------------------|----------| | Analyzes | Compiled HTML in browser | Source templates directly | | Speed | ~10 min for 50 pages | ~3 sec for 50 pages | | Sees | <div class="mat-form-field"> | <mat-form-field> | | AI-ready | Copy/paste from browser | Direct TODO output |

Plus, mat-a11y understands Angular Material:

| What Lighthouse Sees | What mat-a11y Sees | |---------------------|-------------------| | <div class="mat-form-field"> | <mat-form-field> missing a label | | <span class="mat-icon"> | <mat-icon> without aria-label or aria-hidden | | Generic button markup | <button mat-button> missing accessible name | | Rendered dialog HTML | <mat-dialog> not trapping keyboard focus |

Key features:

  • 82 accessibility checks across HTML, SCSS, Angular, Material, and CDK
  • Component-level analysis — each component scored independently for efficient fixing
  • SCSS root cause analysis — collapses duplicate issues to their shared source file
  • AI-optimized output — designed for Claude Opus 4.5, GPT-4, and other LLMs to fix automatically

SCSS Root Cause Analysis

When the same SCSS issue appears across multiple component files, mat-a11y traces @import / @use dependencies to find the shared source file where fixing once fixes everywhere.

Example: 10 components each import _animations.scss, which is missing prefers-reduced-motion. Instead of 10 issues, you get:

[Warning] Add prefers-reduced-motion media query for animations
→ _animations.scss (fixes 10 files)

This reduces your backlog by 50-80% on typical projects. Disable with --no-collapse if you need the raw issue list.

SCSS Variable Resolution

The colorContrast check resolves SCSS variables, CSS custom properties, and color functions before checking contrast ratios. This means projects using design tokens get accurate contrast analysis:

// mat-a11y resolves this chain:
$brand-blue: #1a73e8;
$primary: $brand-blue;
$button-bg: lighten($primary, 10%);

.button {
  background: $button-bg;  // → #4a90e8 (resolved)
  color: white;            // Contrast: 3.2:1 ✗
}

Supported:

  • SCSS variables ($primary-color, $font-size)
  • CSS custom properties (var(--bg), var(--color, #000))
  • SCSS maps (map-get($colors, 'primary'))
  • 20+ color functions (lighten, darken, mix, rgba, adjust-hue, etc.)
  • Chained variables ($a: $b; $b: $c;)

Before: Rules using variables were skipped entirely — a blind spot in well-architected projects.

After: Variables resolve to actual color values, enabling accurate contrast checking even with design tokens.


Real-World Results

mat-a11y was built for traufix.de, a German wedding planning platform with 60+ guides and 300+ Angular components.

Before mat-a11y: Manual accessibility audits, inconsistent fixes, no way to track progress.

After mat-a11y:

Components scanned: 167
Clean (no issues):  115
With issues:         52  → fixed with AI assistance
Total issues:       881  → 0 after fixes

The entire accessibility remediation — from first scan to fully compliant — took less than a day using Claude Opus 4.5 to fix the issues automatically.


Installation

Always install as a devDependency — mat-a11y is a development tool (~1MB) that should never be in your production bundle:

npm install --save-dev mat-a11y

Or run directly without installing:

npx mat-a11y

Bundle Protection: mat-a11y includes safeguards to prevent accidental bundling. If incorrectly installed as a regular dependency, bundlers (webpack, rollup, esbuild) receive a tiny stub (~200 bytes) instead of the full library. The stub logs a warning in development and throws errors if methods are called.

Quick Start

npx mat-a11y

Opens the accessibility dashboard in your browser at http://localhost:3847.

Dashboard features:

  • Plain language issue descriptions (for non-developers)
  • Impact ratings: Critical, High, Medium
  • Clear "How to fix" instructions
  • Visual score overview
  • One-click export to HTML, JSON, CSV
  • Expert mode toggle for advanced options
  • Dark/light mode with system preference detection
  • Severity and category filters (expert mode)
  • CLI command preview with copy button
  • Scan statistics (files, duration, issues by severity)

Try the GUI demo → (static preview)

For scripts and CI/CD:

npx mat-a11y --headless

Runs in headless mode — scans all @Component files, runs 82 checks, outputs _mat-a11y.backlog.txt.

========================================
  MAT-A11Y COMPONENT ANALYSIS
========================================

Tier: FULL
Components scanned: 167
Components with issues: 52

COMPONENT SCORES (167 components):
  Clean (no issues): 115
  Has Issues: 52
  Total Issues: 881

COMPONENTS WITH ISSUES:
  HeaderComponent: 12 issues
  DashboardComponent: 36 issues
  NavigationComponent: 8 issues
  ...

Paste the output into your AI assistant and let it fix the issues component-by-component.


Core Concepts

Scoring

mat-a11y provides two complementary metrics:

| Metric | Formula | Purpose | |--------|---------|---------| | Audit Score | (passing audit weights) / (total weights) × 100 | Severity-weighted, Lighthouse-compatible | | Element Coverage | elementsPassed / elementsChecked × 100 | Actual fix progress |

Audit Score (0-100%) — Used by all formatters, CI thresholds, and reports:

| Score | Status | Meaning | |-------|--------|---------| | 90-100% | Passing | Good shape, minor issues only | | 50-89% | Needs Work | Has accessibility problems to fix | | < 50% | Failing | Significant issues blocking users |

Why two metrics? Audit Score tells you severity — fixing one critical button issue (weight 10) improves your score more than fixing ten minor heading issues (weight 3). Element Coverage tells you progress — how many actual elements you've fixed. Use Audit Score for CI gates, Element Coverage for tracking cleanup work.

Tiers

Choose a tier based on what you're working on:

| Tier | Checks | When to Use | |------|--------|-------------| | --full | 82 | Default. Comprehensive scan | | --basic | 43 | Quick wins for daily development | | --material | 29 | Only Angular Material component issues | | --angular | 10 | Only template and event binding issues |

mat-a11y                         # GUI with full scan (default)
mat-a11y --headless              # Headless with full scan
mat-a11y --headless --basic      # Headless with quick 43-check scan

Analysis Mode

mat-a11y offers three analysis modes:

| Flag | Mode | What It Does | |------|------|--------------| | (default) | Component | Scans all @Component files directly — complete coverage | | --sitemap | Sitemap | Analyzes sitemap URLs + internal routes — SEO/Google crawl view | | --file-based | File | Scans all HTML/SCSS files — legacy mode |

mat-a11y                              # GUI dashboard (default)
mat-a11y --headless                   # Component-based - scans ALL components
mat-a11y --headless --sitemap         # Sitemap + routes analysis (what Google sees)
mat-a11y --headless --file-based      # Legacy file-based analysis
mat-a11y --headless --sitemap --deep  # Page-level (Lighthouse-like)

Default: Component-based analysis — Finds every @Component decorator in your codebase and analyzes its template and styles. This gives you complete coverage of all components, including shared components, widgets, and utilities that may not be in routes.

Note: in component mode, the components list contains only components with issues (to keep output small), while totals/distribution still reflect all analyzed components.

--sitemap mode — Analyzes components based on your sitemap.xml URLs plus internal routes (admin pages, etc.). Shows what Google will crawl and what users will see. Use this for SEO-focused audits.

--deep flag (with --sitemap) — Bundles parent + child components per page for Lighthouse-like scores.

Deep Component Resolution (--deep)

When using --sitemap mode, add --deep for Lighthouse-like page-level analysis that bundles child components per route.

--sitemap (default):           --sitemap --deep (page-level):
HeaderComponent → 3 issues     /home → 47 issues (includes header, nav, footer...)
NavComponent    → 5 issues     /about → 52 issues (same shared components counted again)
FooterComponent → 2 issues     /contact → 49 issues

When to use --sitemap --deep:

  • You want page-level accessibility scores
  • You're comparing against Lighthouse results
  • You need to know total issues per URL

When to use default (component-based):

  • You're fixing issues (fix each component once)
  • You want complete coverage of all components
  • You're tracking fix progress over time
// Programmatic API
const results = analyzeByComponent('./app');                           // Component-based (default)
const sitemapResults = analyzeBySitemap('./app');                      // Sitemap-based
const deepResults = analyzeBySitemap('./app', { deepResolve: true });  // Page-level

Checks

82 checks across 5 categories:

| Category | Count | What It Covers | |----------|-------|----------------| | HTML | 29 | Images, buttons, forms, links, ARIA, headings, tables | | Material | 29 | Form fields, dialogs, icons, menus, tabs, steppers, trees | | SCSS | 14 | Color contrast, focus styles, touch targets, font sizes | | Angular | 7 | Click handlers, keyboard events, routerLinks | | CDK | 3 | Focus trapping, live announcer, aria describer |

mat-a11y --headless --list-checks  # See all 82 with descriptions

Usage Guide

CLI

# Open the accessibility dashboard (default)
mat-a11y

# Headless mode (for scripts/CI)
mat-a11y --headless               # AI backlog output
mat-a11y --headless --html        # Visual HTML report
mat-a11y --headless --sarif       # GitHub Security tab
mat-a11y --ci --junit             # CI/CD pipelines (--ci = --headless)

# Options (headless mode)
mat-a11y --headless ./other-dir   # Custom path
mat-a11y --headless --basic       # Quick 43-check scan
mat-a11y --headless -i "**/*.spec.ts"  # Ignore patterns
mat-a11y --headless -w auto       # Parallel workers

# GUI options
mat-a11y --port 8080              # Custom port for dashboard
mat-a11y [path] [options]

Mode:
  (default)            Opens GUI dashboard in browser
  --headless, -H       CLI mode (no GUI) - outputs to terminal/file
  --ci                 Alias for --headless (CI/CD convenience)
  --port, -p <number>  Custom port for GUI (default: 3847)

Formats (headless mode):
  --html, --json, --sarif, --junit, --github, --gitlab
  --sonar, --checkstyle, --prometheus, --grafana, --datadog
  --slack, --discord, --teams, --markdown, --csv

Tiers:
  --full               All 82 checks (default)
  --basic              Quick 43 checks
  --material           Only mat-* checks (29)
  --angular            Only Angular + CDK checks (10)

Output:
  -f, --format <name>  Output format (ai, json, sarif, etc.)
  -o, --output <path>  Custom output path

Performance:
  -w, --workers <mode> sync (default), auto, or number

Analysis Mode:
  (default)            Component-based - scans all @Component files
  --sitemap            Sitemap + routes analysis (SEO/Google crawl view)
  --file-based         Legacy file-based analysis (HTML/SCSS files only)
  --deep               Page-level analysis (use with --sitemap)
  --no-collapse        Disable SCSS root cause collapse

Options:
  -i, --ignore <pat>   Ignore pattern (repeatable)
  --check <name>       Run single check only
  --list-checks        List all checks
  -h, --help           Show help
  -v, --version        Show version

Example output (default AI format):

# GENERATED by mat-a11y - do not edit
# Fix any issue, re-run: npx mat-a11y

216 issues in 47 files

════════════════════════════════════════
COMPONENT: RepliesComponent
AFFECTS: /verwaltung/rueckmeldungen, /konfigurator/rueckmeldungen/:instanceNumber, /konfigurator/rueckmeldungen
FILES: administration/replies/replies.html, administration/replies/replies.scss, configurator-view/replies/replies.html (+1)
════════════════════════════════════════
buttonNames: <button mat-icon-button color="primary" (click)="viewRe... (×2)
  → Add text content inside <button>
headingOrder: <h3>
  → Use h2 instead of h3
matIconAccessibility: <mat-icon class="large-icon">question_answer</mat-icon> (×18)
  → Add aria-label to parent button
...

Full example: https://robspan.github.io/mat-a11y/_report-ai.backlog.txt

Parallel Processing

For large codebases (500+ files) in headless mode:

mat-a11y --headless              # Single-threaded (default)
mat-a11y --headless -w auto      # Auto-optimized workers
mat-a11y --headless -w 8         # Exactly 8 workers

| Project Size | Sync | Auto (-w auto) | |--------------|------|------------------| | ~100 files | ~60ms | ~60ms (same) | | ~500 files | ~2.8s | ~0.8s (3.5x faster) |

Benchmarked on AMD Ryzen 9 8940HX (16 cores / 32 threads)

How it works: In auto mode, the runner calculates the optimal worker count (~50 files per worker). For 500 files, it uses ~10 workers regardless of CPU count, avoiding overhead from too many workers. For small projects (<100 files), auto falls back to single-threaded mode.

When to use:

  • sync (default): Predictable, no async - works everywhere
  • -w auto: Large codebases (500+ files) for significant speedup
  • -w <number>: When you want explicit control

Output Formats

17 formats. All work as --shortcut flags. See all example outputs →

| Category | Shortcuts | Output File | Example | |----------|-----------|-------------|---------| | AI | (default) | _mat-a11y.backlog.txt | View | | Reports | --json | _mat-a11y.json | View | | | --html | _mat-a11y.html | View | | | --raw | _mat-a11y.raw.json | View | | CI/CD | --sarif | _mat-a11y.sarif.json | View | | | --junit | _mat-a11y.junit.xml | View | | | --github | _mat-a11y.github.txt | View | | | --gitlab | _mat-a11y.gitlab.json | View | | Quality | --sonar | _mat-a11y.sonar.json | View | | | --checkstyle | _mat-a11y.checkstyle.xml | View | | Monitoring | --prometheus | _mat-a11y.prom | View | | | --grafana | _mat-a11y.grafana.json | View | | | --datadog | _mat-a11y.datadog.json | View | | Notifications | --slack | _mat-a11y.slack.json | View | | | --discord | _mat-a11y.discord.json | View | | | --teams | _mat-a11y.teams.json | View | | Data | --markdown | _mat-a11y.md | View | | | --csv | _mat-a11y.csv | View |

Custom output: mat-a11y --headless -f sarif -o custom-name.sarif

AI-Assisted Fixing

The headless mode output (_mat-a11y.backlog.txt) is designed for AI to fix:

Tips:

  • Component-by-component — Issues grouped by component for systematic fixing
  • AFFECTS line — Shows which URLs improve when you fix a component
  • Counts(×3) means 3 identical elements in that component
  • Verify — Re-run mat-a11y --headless after fixes; list shrinks

Prompt example:

Read _mat-a11y.backlog.txt. For each file, apply the fixes, then re-run `npx mat-a11y --headless` to update the backlog.

Validated models:

| Model | Status | |-------|--------| | Claude Opus 4.5 | ✅ Validated (our daily driver) | | Claude Sonnet 4 | ✅ Validated | | GPT-4o | Should work | | Cursor / Windsurf | Should work |

mat-a11y does not include or require any AI. Bring your own.

CI/CD Integration

# .github/workflows/a11y.yml
name: Accessibility
on: [push, pull_request]

jobs:
  a11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
      - run: npm ci
      - run: npx mat-a11y --ci --json
      - uses: actions/upload-artifact@v4
        with:
          name: a11y-report
          path: _mat-a11y.json

Exit codes: 0 = passing, 1 = failing, 2 = error


Programmatic API

const {
  analyzeByComponent,  // Component-based (default, recommended)
  analyzeBySitemap,    // Sitemap + routes (SEO view)
  analyze,             // File-based (legacy)
  basic, material, angular, full  // Shortcuts
} = require('mat-a11y');

// Component-based analysis (default)
const results = analyzeByComponent('./app', { tier: 'full' });
console.log(`Components: ${results.componentCount}, Issues: ${results.totalIssues}`);

// Sitemap-based analysis (SEO view)
const sitemapResults = analyzeBySitemap('./app', { tier: 'full' });
console.log(`URLs: ${sitemapResults.urlCount}`);

// Control parallelization (file-based only)
const syncResults = analyze('./app', { tier: 'full' }); // sync (default)
const autoResults = await analyze('./app', { tier: 'full', workers: 'auto' }); // auto (async)
const { formatters } = require('mat-a11y');

formatters.listFormatters();           // ['sarif', 'junit', ...]
formatters.format('sarif', results);   // Formatted string
formatters.getFormatter('junit');      // Formatter module
import {
  analyze, analyzeBySitemap, analyzeByRoute,
  Tier, AnalysisResult, SitemapAnalysisResult,
  UrlResult, Issue, AuditResult,
  formatters, Formatter
} from 'mat-a11y';

type Tier = 'basic' | 'material' | 'angular' | 'full';

interface UrlResult {
  url: string;
  auditScore: number;  // 0-100
  issues: Issue[];
  audits: AuditResult[];
}

Full types: src/index.d.ts

HTML Checks (29)

| Check | Weight | WCAG | Description | |-------|--------|------|-------------| | buttonNames | 10 | 4.1.2 | Buttons must have accessible names | | imageAlt | 10 | 1.1.1 | Images must have alt text | | formLabels | 10 | 1.3.1 | Form inputs must have labels | | linkNames | 10 | 2.4.4 | Links must have accessible names | | ariaRoles | 7 | 4.1.2 | ARIA roles must be valid | | ariaAttributes | 7 | 4.1.2 | ARIA attributes must be valid | | uniqueIds | 7 | 4.1.1 | IDs must be unique | | headingOrder | 3 | 1.3.1 | Headings in logical order | | tableHeaders | 7 | 1.3.1 | Tables must have headers | | iframeTitles | 7 | 2.4.1 | Iframes must have titles | | listStructure | 3 | 1.3.1 | Lists use proper structure | | dlStructure | 3 | 1.3.1 | Definition lists structured | | videoCaptions | 10 | 1.2.2 | Videos must have captions | | objectAlt | 7 | 1.1.1 | Objects need text alternatives | | accesskeyUnique | 3 | 4.1.1 | Accesskeys must be unique | | tabindex | 3 | 2.4.3 | No positive tabindex | | ariaHiddenBody | 10 | 4.1.2 | Body not aria-hidden | | htmlHasLang | 7 | 3.1.1 | HTML has lang attribute | | metaViewport | 7 | 1.4.4 | Viewport allows zoom | | skipLink | 3 | 2.4.1 | Page has skip link | | inputImageAlt | 7 | 1.1.1 | Input images need alt | | autoplayMedia | 3 | 1.4.2 | No autoplay media | | marqueeElement | 7 | 2.2.2 | No marquee element | | blinkElement | 7 | 2.2.2 | No blink element | | metaRefresh | 3 | 2.2.1 | No auto-refresh | | duplicateIdAria | 7 | 4.1.1 | ARIA IDs unique | | emptyTableHeader | 3 | 1.3.1 | Table headers not empty | | scopeAttrMisuse | 3 | 1.3.1 | Scope used correctly | | formFieldName | 7 | 4.1.2 | Form fields have names |

Angular Material Checks (29)

| Check | Weight | Description | |-------|--------|-------------| | matFormFieldLabel | 10 | mat-form-field has label | | matSelectPlaceholder | 7 | mat-select has placeholder/label | | matAutocompleteLabel | 7 | mat-autocomplete has label | | matDatepickerLabel | 7 | mat-datepicker has label | | matRadioGroupLabel | 7 | mat-radio-group has label | | matSlideToggleLabel | 7 | mat-slide-toggle has label | | matCheckboxLabel | 7 | mat-checkbox has label | | matChipListLabel | 7 | mat-chip-list has label | | matSliderLabel | 7 | mat-slider has label | | matButtonType | 3 | mat-button has type | | matIconAccessibility | 10 | mat-icon has aria-label/hidden | | matButtonToggleLabel | 7 | mat-button-toggle has label | | matProgressBarLabel | 7 | mat-progress-bar has label | | matProgressSpinnerLabel | 7 | mat-progress-spinner has label | | matBadgeDescription | 3 | mat-badge has description | | matMenuTrigger | 7 | mat-menu trigger has aria | | matSidenavA11y | 7 | mat-sidenav accessible | | matTabLabel | 7 | mat-tab has label | | matStepLabel | 7 | mat-step has label | | matExpansionHeader | 7 | mat-expansion-panel has header | | matTreeA11y | 7 | mat-tree accessible | | matListSelectionLabel | 7 | mat-selection-list has label | | matTableHeaders | 7 | mat-table has headers | | matPaginatorLabel | 3 | mat-paginator has labels | | matSortHeaderAnnounce | 3 | mat-sort-header announces | | matDialogFocus | 10 | mat-dialog manages focus | | matBottomSheetA11y | 7 | mat-bottom-sheet accessible | | matTooltipKeyboard | 3 | mat-tooltip keyboard accessible | | matSnackbarPoliteness | 3 | mat-snackbar politeness set |

SCSS Checks (14)

| Check | Weight | Description | |-------|--------|-------------| | colorContrast | 7 | Text contrast >= 4.5:1 | | focusStyles | 10 | Focus states visible | | touchTargets | 7 | Touch targets >= 44x44px | | outlineNoneWithoutAlt | 7 | outline:none has alternative | | prefersReducedMotion | 3 | Respects reduced-motion | | userSelectNone | 3 | user-select:none usage | | pointerEventsNone | 3 | pointer-events:none usage | | visibilityHiddenUsage | 3 | visibility:hidden usage | | focusWithinSupport | 3 | :focus-within support | | hoverWithoutFocus | 7 | :hover has :focus pair | | contentOverflow | 3 | Content overflow handled | | smallFontSize | 7 | Font size >= 12px | | lineHeightTight | 3 | Line height >= 1.5 | | textJustify | 3 | No text-align: justify |

Angular Checks (7)

| Check | Weight | Description | |-------|--------|-------------| | clickWithoutKeyboard | 10 | (click) has keyboard handler | | clickWithoutRole | 7 | (click) on non-button has role | | routerLinkNames | 7 | routerLink has accessible name | | ngForTrackBy | 3 | *ngFor uses trackBy | | innerHtmlUsage | 3 | [innerHTML] security | | asyncPipeAria | 3 | async pipe with aria | | autofocusUsage | 3 | autofocus usage |

CDK Checks (3)

| Check | Weight | Description | |-------|--------|-------------| | cdkTrapFocusDialog | 10 | Dialogs trap focus | | cdkAriaDescriber | 7 | CDK aria describer | | cdkLiveAnnouncer | 7 | CDK live announcer |


Requirements

  • Node.js >= 16
  • Angular >= 12
  • Angular Material >= 12

Limitations

  • Static analysis only — scans source code, not the running app
  • CSS variables — can't resolve contrast for var(--custom-color)
  • Not a replacement — use alongside Lighthouse and manual testing

FAQ

How is this different from Lighthouse?

Lighthouse analyzes rendered HTML in a browser. mat-a11y analyzes your source code and understands Angular Material component patterns that Lighthouse can't see. Use both together.

Does this replace manual testing?

No. Automated tools catch ~30-50% of accessibility issues. mat-a11y finds what it can; you still need keyboard testing, screen reader testing, and user testing.

Can I add custom checks?

Yes. Create a check module in src/checks/ following the existing pattern. See Contributing.

Why static analysis instead of runtime?

Static analysis runs fast (no browser needed), integrates easily into CI/CD, and catches issues before code is even compiled. Runtime testing is complementary, not a replacement.


Contributing

All 82 checks and 17 formatters were developed using Test-Driven Development (TDD). Each check has a verify file (dev/tests/verify-files/<checkName>.html or .scss) with @a11y-pass and @a11y-fail sections that define expected behavior. The full test fixtures and verification scripts are available in the GitHub repository.

git clone https://github.com/robspan/mat-a11y
cd mat-a11y
npm test           # Structure + formatter verification
npm run dev-check  # Full verification including self-test

What's in the Repo vs npm

| Folder | In npm? | Description | |--------|---------|-------------| | src/ | Yes | 82 checks, 17 formatters, core engine | | bin/ | Yes | CLI entry point | | dev/ | No | Verification scripts, tests, contributor guide | | example-outputs/ | No | Sample outputs for all formats |

Adding a Check

  1. Create src/checks/myCheck.js with name, type, tier, weight, check()
  2. Create dev/tests/verify-files/myCheck.html with @a11y-pass, @a11y-fail, @a11y-false-positive, @a11y-false-negative sections
  3. Run npm test

Adding a Formatter

  1. Create src/formatters/myFormat.js with name, category, output, format()
  2. Run npm test

Full docs: dev/README.md


Community


Author

Built for traufix.de — a German wedding planning platform with 60+ guides. Created to ensure Angular Material components meet WCAG standards at scale.

Robin Spanier robspan.de · [email protected]

License

MIT + Commons Clause — Free to use, modify, and distribute. Not for resale.