eslint-plugin-jsx-a11y

repository·main·Indexed 25 days ago

https://github.com/jsx-eslint/eslint-plugin-jsx-a11y

A static AST checker for accessibility rules on JSX elements, designed to catch common accessibility errors in React applications during development. It provides a wide range of rules covering ARIA validation, interactive elements, anchors, links, labels, forms, images, media, and keyboard navigation. The plugin supports both legacy .eslintrc and Flat Config (eslint.config.js) formats, offering recommended and strict configurations.

Tokens
19.9K
Snippets
75
Records
107
Agent score
83%

What's inside eslint-plugin-jsx-a11y

  1. Understand the jsx-a11y/no-noninteractive-tabindex rule

    main

    The jsx-a11y/no-noninteractive-tabindex rule ensures that tabIndex is only applied to elements that are actually interactive. Adding tabIndex to non-interactive elements (like <div>, <article>, or <li>) increases the size of the page's tab ring unnecessarily, as assistive technologies already provide mechanisms to traverse these containers.

    This rule is enabled by default in the recommended and strict configurations.

    Common interactive roles that ARE allowed to have a tabindex include:

    • button
    • link
    • checkbox
    • menuitem
    • menuitemcheckbox
    • menuitemradio
    • option
    • radio
    • searchbox
    • switch
    • textbox
  2. Understand the jsx-a11y/anchor-is-valid rule

    main

    The jsx-a11y/anchor-is-valid rule ensures that <a> elements are used as true hyperlinks. An anchor is considered valid if it has an href attribute containing a valid URL or internal location. This rule prevents using anchors as buttons or using invalid href values like javascript:void(0) or #, which can break accessibility for keyboard and screen reader users.

    This rule is enabled by default in the recommended and strict configurations.

  3. Understand the jsx-a11y/no-static-element-interactions rule

    main

    The jsx-a11y/no-static-element-interactions rule prevents adding interactive event listeners (like mouse or key events) to static HTML elements (e.g., <div>, <span>, or elements without semantic mapping like <footer>) without providing an appropriate WAI-ARIA role.

    Static elements lack semantic meaning in the accessibility layer. To make them interactive for assistive technology, you must assign a role that describes the element's purpose.

    This rule is enabled by default in the recommended and strict configurations.

  4. Understand the jsx-a11y/no-noninteractive-element-interactions rule

    main

    The jsx-a11y/no-noninteractive-element-interactions rule prevents adding event handlers (like onClick or onKeyPress) to non-interactive HTML elements or elements with non-interactive ARIA roles.

    Non-interactive elements (e.g., <main>, <p>, <img>, <ul>) and roles (e.g., article, listitem, tooltip) are intended for content and containers, not for user interaction. Adding handlers to them can confuse assistive technologies.

    This rule is enabled by default in the recommended and strict configurations.

  5. Install eslint-plugin-jsx-a11y

    main

    To use this plugin, you must first install ESLint, then install eslint-plugin-jsx-a11y as a development dependency. If you installed ESLint globally, you must also install this plugin globally.

    Note: If you are using eslint-config-airbnb, follow the specific instructions provided by the Airbnb repository.

    # Install ESLint
    # npm
    npm install eslint --save-dev
    
    # yarn
    yarn add eslint --dev
    
    # Install eslint-plugin-jsx-a11y
    # npm
    npm install eslint-plugin-jsx-a11y --save-dev
    
    # yarn
    yarn add eslint-plugin-jsx-a11y --dev
  6. Resolve no-noninteractive-element-interactions: Element acting as a button or link

    main

    If a non-interactive element is being used as a control (like a button or link), move the event handler to an inner element that is semantically interactive (e.g., <button>, <a href>) or has an appropriate interactive ARIA role (e.g., button, link, checkbox, menuitem, option, radio, switch, textbox).

    Note: Adding a role does not automatically add keyboard behavior (like Enter key support). You must manually implement focusability and key press support.

  7. Scaffold a new rule using create-rule

    main

    If you are contributing to this project by developing new rules, you can use the create-rule script to scaffold the necessary files for a new rule.

    Run the following command from the repository root, replacing my-new-rule with your desired rule name:

    ./scripts/create-rule.js my-new-rule
  8. Use the jsx-a11y/anchor-has-content rule

    main

    The jsx-a11y/anchor-has-content rule ensures that anchor elements (or specified components) have accessible content for screen readers. Content is considered accessible if it is not hidden via the aria-hidden prop.

    An anchor is valid if it contains:

    • Text content
    • Other JSX elements that are not hidden
    • A title prop
    • An aria-label prop
    • Content provided via dangerouslySetInnerHTML

    This rule is included in the recommended and strict configurations.

    // Succeeding examples
    <a>Anchor Content!</a>
    <a><TextWrapper /></a>
    <a dangerouslySetInnerHTML={{ __html: 'foo' }} />
    <a title='foo' />
    <a aria-label='foo' />
    
    // Failing examples
    <a />
    <a><TextWrapper aria-hidden /></a>
  9. Use Shareable Flat Configs in eslint.config.js

    main

    The plugin provides two shareable flat configurations: flatConfigs.recommended and flatConfigs.strict.

    Important: These configs do NOT configure files or languageOptions.globals. You should spread the config and then manually define your file patterns and globals to ensure they apply correctly to your project.

    // ESM Example
    import jsxA11y from 'eslint-plugin-jsx-a11y';
    const globals = require('globals');
    
    export default [
      {
        files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
        ...jsxA11y.flatConfigs.recommended,
        languageOptions: {
          ...jsxA11y.flatConfigs.recommended.languageOptions,
          globals: {
            ...globals.serviceworker,
            ...globals.browser,
          },
        },
      },
    ];
  10. Resolve no-noninteractive-element-interactions: Expandable headings

    main

    When a heading (e.g., <h3>) is used to expand or collapse content (like an accordion), do not put the interaction handler on the heading itself. Instead, place a <button> inside the heading to handle the interaction. This preserves both the heading's role and the button's interactive role.

    <h3>
      <button onClick={this._expandSection}>News</button>
    </h3>
    <ul id="articles-list">
      <li...>...</li>
    </ul>
  11. Generate a new rule boilerplate with create-rule.js

    main

    Use the scripts/create-rule.js script to scaffold a new rule. This generates three boilerplate files:

    1. src/rules/${rule-name}.js (the rule implementation)
    2. __tests__/src/rules/${rule-name}-test.js (the rule tests)
    3. docs/rules/${rule-name}.md (the rule documentation)

    Note: If the rule name already exists or is formatted incorrectly, the script will throw an error.

    $ node scripts/create-rule.js rule-name --author="Your name" --description="Description of the rule"
  12. Resolve jsx-a11y/no-noninteractive-tabindex errors

    main

    If you encounter this error, consider the following resolutions based on your use case:

    1. Using <a> tags: A bare <a> tag without an href is non-interactive. To make it interactive, add an href attribute (which gives it the link role) or an explicit role="button".
    2. Using semantic HTML: Do not add tabIndex to elements like <article> or <li>. Assistive technologies already allow users to navigate these.
    3. Scrollable containers: If you need to make a scrollable container keyboard-focusable for compatibility with certain browsers, you may add tabIndex="0". In this case, you should disable the rule for that specific line.
    4. Composite widgets: If an element must capture tab traversal for a complex widget, disable the rule on a per-instance basis using an ESLint disable comment.
    // eslint-disable-next-line no-noninteractive-tabindex
    <pre tabIndex="0">
      <code>{someLongCode}</code>
    </pre>