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.
Maintainers
Readme
mat-a11y
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-a11yOpens 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.txt— AI backlog samplenpx mat-a11y --headless --html→_mat-a11y.html— HTML samplenpx mat-a11y --headless --json→_mat-a11y.json— JSON sample
npx mat-a11y --headless --sarif— SARIF samplenpx mat-a11y --headless --junit— JUnit XML samplenpx mat-a11y --headless --github— GitHub Annotations samplenpx mat-a11y --headless --gitlab— GitLab Code Quality samplenpx mat-a11y --headless --sonar— SonarQube samplenpx mat-a11y --headless --checkstyle— Checkstyle XML samplenpx mat-a11y --headless --prometheus— Prometheus samplenpx mat-a11y --headless --grafana— Grafana JSON samplenpx mat-a11y --headless --datadog— Datadog samplenpx mat-a11y --headless --slack— Slack samplenpx mat-a11y --headless --discord— Discord samplenpx mat-a11y --headless --teams— MS Teams samplenpx mat-a11y --headless --markdown— Markdown samplenpx mat-a11y --headless --csv— CSV 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:
- mat-a11y finds every accessibility issue in your codebase (static analysis)
- mat-a11y generates a structured TODO list optimized for AI
- 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 fixesThe 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-a11yOr run directly without installing:
npx mat-a11yBundle 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-a11yOpens 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 --headlessRuns 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 scanAnalysis 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 issuesWhen 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-levelChecks
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 descriptionsUsage 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 dashboardmat-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 versionExample 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 --headlessafter 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.jsonExit 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 moduleimport {
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-testWhat'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
- Create
src/checks/myCheck.jswithname,type,tier,weight,check() - Create
dev/tests/verify-files/myCheck.htmlwith@a11y-pass,@a11y-fail,@a11y-false-positive,@a11y-false-negativesections - Run
npm test
Adding a Formatter
- Create
src/formatters/myFormat.jswithname,category,output,format() - Run
npm test
Full docs: dev/README.md
Community
- Issues & Features: GitHub Issues
- Discussions: GitHub Discussions
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.
