The a11y ESLint plugin provides several configuration presets to suit different project needs.
The minimal configuration enables only the most critical accessibility rules. Use this for:
- Large projects starting accessibility checks
- Incremental adoption
- Performance-sensitive environments
// .eslintrc.js
module.exports = {
plugins: ['a11y'],
extends: ['plugin:a11y/minimal']
}Rule Severity:
button-label: error (critical impact)form-label: error (critical impact)image-alt: error (serious impact)
When to use:
- Starting accessibility checks in large codebase
- Need fast ESLint execution
- Want to focus on critical violations first
See Large Project Setup Guide for detailed instructions.
The recommended configuration uses a balanced approach with critical violations as errors and moderate violations as warnings.
// .eslintrc.js
module.exports = {
plugins: ['a11y'],
extends: ['plugin:a11y/recommended']
}Rule Severity:
image-alt: error (serious impact)button-label: error (critical impact)form-label: error (critical impact)link-text: warn (moderate impact)heading-order: warn (moderate impact)
The strict configuration treats all violations as errors for maximum enforcement.
// .eslintrc.js
module.exports = {
plugins: ['a11y'],
extends: ['plugin:a11y/strict']
}Rule Severity:
- All rules:
error
Use this configuration when you want to enforce strict accessibility standards and catch all violations immediately.
Optimized configuration for React/JSX projects.
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
}
},
plugins: ['a11y'],
extends: ['plugin:a11y/react']
}Features:
- Pre-configured for JSX parsing
- Same rule severity as recommended
- Optimized for React component patterns
Optimized configuration for Vue projects using vue-eslint-parser.
// .eslintrc.js
module.exports = {
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module'
},
plugins: ['a11y'],
extends: ['plugin:a11y/vue']
}Features:
- Pre-configured for Vue SFC templates
- Same rule severity as recommended
- Optimized for Vue template syntax
Note: Requires vue-eslint-parser to be installed.
You can also configure rules individually:
// .eslintrc.js
module.exports = {
plugins: ['a11y'],
extends: ['plugin:a11y/recommended'],
rules: {
// Override specific rules
'a11y/image-alt': 'error',
'a11y/link-text': 'warn',
// Disable a rule
'a11y/heading-order': 'off',
// Use rule with options
'a11y/image-alt': ['error', {
allowMissingAltOnDecorative: true,
decorativeMatcher: {
markerAttributes: ['data-decorative']
}
}],
'a11y/link-text': ['warn', {
denylist: ['click here', 'read more'],
caseInsensitive: true
}],
'a11y/heading-order': ['warn', {
allowSameLevel: true,
maxSkip: 2
}]
}
}Configure how decorative images are handled:
{
'a11y/image-alt': ['error', {
allowMissingAltOnDecorative: false, // default: false (strict by default)
decorativeMatcher: {
requireAriaHidden: false, // Require aria-hidden="true"
requireRolePresentation: false, // Require role="presentation"
markerAttributes: [] // Custom attributes like ['data-decorative']
}
}]
}Examples:
// Default behavior (strict) - requires alt
<img src="photo.jpg" /> // ❌ Error: missing alt
// With allowMissingAltOnDecorative: true
<img src="decorative.jpg" aria-hidden="true" /> // ✅ Allowed
<img src="decorative.jpg" role="presentation" /> // ✅ Allowed
<img src="decorative.jpg" data-decorative="true" /> // ✅ Allowed (with markerAttributes)
// Empty alt without decorative markers
<img src="photo.jpg" alt="" /> // ❌ Error: emptyAltNotDecorativeConfigure denylist and matching behavior:
{
'a11y/link-text': ['warn', {
denylist: ['click here', 'read more', 'more'], // default
caseInsensitive: true, // default: true
allowlistPatterns: [] // Regex patterns to allow
}]
}Examples:
// Default denylist
<a href="/about">Click here</a> // ⚠️ Warning: nonDescriptive
// Custom denylist
{
'a11y/link-text': ['warn', {
denylist: ['learn more', 'discover']
}]
}
// Case sensitive matching
{
'a11y/link-text': ['warn', {
caseInsensitive: false
}]
}
// <a href="/about">CLICK HERE</a> // ✅ Passes (case sensitive)
// <a href="/about">click here</a> // ⚠️ Warning
// Checks multiple accessible name sources
<a href="/about" aria-label="About our company">Click here</a> // ✅ Passes (aria-label checked)
<a href="/about" aria-labelledby="link-label">Click here</a> // ✅ Passes (aria-labelledby checked)Configure heading hierarchy tolerance:
{
'a11y/heading-order': ['warn', {
allowSameLevel: true, // default: true
maxSkip: undefined // Allow skips up to this level (e.g., 2 allows h1→h3)
}]
}Examples:
// Default: allows same level
<h2>First</h2><h2>Second</h2> // ✅ Passes
// Allow larger skips
{
'a11y/heading-order': ['warn', {
maxSkip: 2 // Allows skipping up to 2 levels
}]
}
// <h1>Title</h1><h3>Section</h3> // ✅ Passes (skip of 2)
// <h1>Title</h1><h4>Subsection</h4> // ⚠️ Warning (skip of 3 > maxSkip)Map your design-system components to native HTML elements so rules apply correctly.
// .eslintrc.js
module.exports = {
plugins: ['a11y'],
extends: ['plugin:a11y/recommended'],
settings: {
'a11y': {
components: {
Link: 'a', // Treat <Link> as <a>
Button: 'button', // Treat <Button> as <button>
Image: 'img' // Treat <Image> as <img>
}
}
}
}Example:
// Without mapping - rules don't apply
<Link href="/about">Click here</Link> // No warning
// With mapping - rules apply
// .eslintrc.js settings: { 'a11y': { components: { Link: 'a' } } }
<Link href="/about">Click here</Link> // ⚠️ Warning: nonDescriptiveSupport components that accept an as or component prop:
// .eslintrc.js
module.exports = {
plugins: ['a11y'],
extends: ['plugin:a11y/recommended'],
settings: {
'a11y': {
polymorphicPropNames: ['as', 'component'] // Default: ['as', 'component']
}
}
}Example:
// Polymorphic component
<Link as="a" href="/about">About Us</Link> // ✅ Treated as <a>
<Link as="button" onClick={handleClick}>Click</Link> // ✅ Treated as <button>
// Dynamic polymorphic (not checked statically)
<Link as={component} href="/about">About</Link> // ✅ No error (dynamic)Component resolution follows this order (highest to lowest priority):
- Native HTML tag -
<a>,<button>,<img>always win - Polymorphic prop -
as="a"orcomponent="button"(when static literal) - Settings mapping -
components: { Link: 'a' } - Unknown - Component not recognized
For ESLint v9+, use flat config format with our presets:
// eslint.config.js
import testA11yJs from 'eslint-plugin-a11y'
export default [
{
plugins: {
'a11y': testA11yJs
},
...testA11yJs.configs['flat/recommended']
}
]// eslint.config.js
import testA11yJs from 'eslint-plugin-a11y'
export default [
{
plugins: {
'a11y': testA11yJs
},
...testA11yJs.configs['flat/react']
}
]// eslint.config.js
import testA11yJs from 'eslint-plugin-a11y'
export default [
{
plugins: {
'a11y': testA11yJs
},
...testA11yJs.configs['flat/vue']
}
]flat/recommended- Rules only (minimal assumptions, add your own parser)flat/recommended-react- Rules + React parser options (convenience)flat/react- Full React setupflat/vue- Full Vue setupflat/minimal- Minimal rules onlyflat/strict- All rules as errors
// eslint.config.js
import testA11yJs from 'eslint-plugin-a11y'
export default [
{
plugins: {
'a11y': testA11yJs
},
...testA11yJs.configs['flat/recommended'],
settings: {
'a11y': {
components: {
Link: 'a',
Button: 'button'
},
polymorphicPropNames: ['as']
}
}
}
]Rules are categorized by impact level:
- button-label: Buttons without labels prevent screen reader users from understanding functionality
- form-label: Form controls without labels prevent users from understanding what to input
- image-alt: Images without alt text prevent screen reader users from understanding content
- link-text: Non-descriptive link text makes navigation difficult for screen reader users
- heading-order: Skipped heading levels make document structure unclear
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
}
},
plugins: ['a11y'],
extends: ['plugin:a11y/react']
}// .eslintrc.js
module.exports = {
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
},
plugins: ['a11y'],
extends: ['plugin:a11y/vue']
}Works with both React and Vue:
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
}
},
plugins: ['a11y', '@typescript-eslint'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:a11y/recommended'
]
}If you want to upgrade from recommended to strict:
// Before
extends: ['plugin:a11y/recommended']
// After
extends: ['plugin:a11y/strict']This will change link-text and heading-order from warnings to errors.
If migrating a React project to Vue:
// Before (React)
parser: '@typescript-eslint/parser',
extends: ['plugin:a11y/react']
// After (Vue)
parser: 'vue-eslint-parser',
extends: ['plugin:a11y/vue']- Start with Recommended: Use the recommended configuration as a starting point
- Gradually Increase Strictness: Move to strict configuration once your codebase is mostly compliant
- Framework-Specific Configs: Use React or Vue configs for better integration
- Custom Overrides: Override specific rules based on your project's needs
- CI/CD Integration: Use strict configuration in CI/CD to catch violations early
For large codebases, you may want to exclude certain files or directories from accessibility checks.
// .eslintrc.js
module.exports = {
plugins: ['a11y'],
extends: ['plugin:a11y/recommended'],
ignorePatterns: [
// Exclude build outputs
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/out/**',
// Exclude dependencies
'**/node_modules/**',
// Exclude test files (optional)
'**/*.test.{js,ts,jsx,tsx}',
'**/*.spec.{js,ts,jsx,tsx}',
// Exclude generated files
'**/*.generated.{js,ts}',
'**/generated/**',
// Exclude legacy code (temporary)
'**/legacy/**',
'**/old/**'
]
}Create a .eslintignore file in your project root:
# Build outputs
dist/
build/
.next/
out/
# Dependencies
node_modules/
# Test files (optional)
**/*.test.{js,ts,jsx,tsx}
**/*.spec.{js,ts,jsx,tsx}
# Legacy code
legacy/
old/
For specific files that need exceptions:
// .eslintrc.js
module.exports = {
plugins: ['a11y'],
extends: ['plugin:a11y/recommended'],
overrides: [
{
files: ['**/*.test.{js,ts,jsx,tsx}'],
rules: {
'a11y/**': 'off' // Disable all a11y rules in tests
}
},
{
files: ['**/legacy/**'],
rules: {
'a11y/**': 'warn' // Only warnings in legacy code
}
}
]
}- Ensure the plugin is installed:
npm install eslint-plugin-a11y - Verify the plugin is in your ESLint config
- Check that your parser supports JSX (for React) or Vue (for Vue)
- Ensure file extensions are included in ESLint's file patterns
If you're getting too many violations:
- Start with minimal configuration (only 3 critical rules)
- Fix violations incrementally
- Use
eslint-disablecomments for exceptions - Gradually move to recommended, then strict configuration
- Ensure
vue-eslint-parseris installed - Verify your ESLint config uses
vue-eslint-parseras the parser - Check that
.vuefiles are included in ESLint's file patterns