eslint-plugin-vuejs-accessibility

repository·main·Indexed 43 days ago

https://github.com/vue-a11y/eslint-plugin-vuejs-accessibility

An ESLint plugin for checking accessibility (a11y) rules within Vue Single File Components (.vue files). Version 2.6.0 provides rules to enforce alternative text, valid ARIA roles and properties, keyboard event listeners for click events, and programmatically associated labels for form controls.

Tokens
12.8K
Snippets
49
Records
60
Agent score
55%

What's inside eslint-plugin-vuejs-accessibility

  1. Understand the `no-static-element-interactions` rule

    main

    The no-static-element-interactions rule prevents adding interactivity (like mouse or key event listeners) to static HTML elements that lack semantic meaning.

    Static elements like <div> or <span> have no semantic mapping in the accessibility layer. Even elements that appear semantic (e.g., <time>, <footer>, <picture>) may lack a semantic mapping. To make an interactive element accessible, you must provide a semantic mapping using the WAI-ARIA role attribute so assistive technologies can communicate the element's purpose to the user.

  2. Use the role-has-required-aria-props rule

    main

    The role-has-required-aria-props rule ensures that any HTML element assigned an ARIA role includes all the mandatory ARIA attributes required by that specific role. This prevents accessibility gaps where a role is declared but the necessary state or property attributes (like aria-checked for a checkbox) are missing.

    <!-- Succeed: checkbox role includes required aria-checked -->
    <template>
      <span
        role="checkbox"
        aria-checked="false"
        aria-labelledby="test"
        tabindex="0"
      />
    </template>
    
    <!-- Fail: checkbox role is missing aria-checked -->
    <template>
      <span role="checkbox" aria-labelledby="test" tabindex="0" />
    </template>
  3. Use the tabindex-no-positive rule

    main

    The tabindex-no-positive rule prevents the use of positive integer values for the tabindex attribute in Vue templates. Using positive values (e.g., tabindex="1") disrupts the natural keyboard tab order and makes it difficult for users to predict the focus flow.

    To maintain accessibility, only use 0 (to include an element in the natural tab order) or -1 (to make an element programmatically focusable but remove it from the tab order).

    <!-- ✅ Succeed: Using 0 or -1 is allowed -->
    <span tabindex="0">foo</span>
    <span tabindex="-1">bar</span>
    <span :tabindex="0">baz</span>
    
    <!-- ❌ Fail: Positive integers are prohibited -->
    <span tabindex="5">foo</span>
    <span tabindex="3">bar</span>
    <span tabindex="1">baz</span>
  4. Resolve 'interactive role must be tabbable' error

    main

    This error occurs when a standalone control (like a button or link) with an interactive role and interaction handlers is not reachable via the Tab key. To fix this, you have two options:

    1. Use semantic HTML: Replace the component with a native element that is inherently part of the tab ring, such as <button>, <a href>, or <input>.
    2. Add tabindex="0": If you must use a non-semantic element, add tabindex="0" to make it tabbable.
    <template>
      <div role="button" @click="foo" tabindex="0" />
    </template>
  5. How to add a new rule

    main

    When adding a new accessibility rule to the plugin, follow these steps to ensure it is correctly integrated, tested, and documented:

    1. Register the rule: Add the rule's configuration and require statement to src/index.ts.
    2. Implement the rule: Add the rule logic itself into the src/rules directory.
    3. Test the rule: Add corresponding tests in src/rules/__tests__.
    4. Document the rule: Add the rule's documentation in docs/rules.
  6. Use the click-events-have-key-events rule

    main

    The click-events-have-key-events rule ensures that any element using a @click event listener is also accompanied by at least one keyboard event listener (@keyup, @keydown, or @keypress). This is necessary to support users with physical disabilities who rely on keyboards instead of mice, as well as screen reader and AT (Assistive Technology) users.

    This rule takes no configuration arguments.

    <!-- This passes because it includes a keyboard event -->
    <div @click="foo" @keydown="bar" />
    
    <!-- This fails because it only has a click event -->
    <div @click="foo" />
  7. Resolve 'interactive role must be focusable' error

    main

    This error occurs when an element is part of a group (like a menu or button bar) or a composite widget. In these cases, elements should be focusable but not necessarily tabbable to allow for standard keyboard interaction patterns (e.g., using arrow keys to navigate).

    To fix this, assign tabindex="-1" to the elements in the group. Ensure that at least one item in the group has tabindex="0" so the user can tab into the component initially. Once focus is inside, you must implement manual key handling (up/down/left/right) to move focus between the elements.

    <template>
      <div role="menu">
        <div role="menuitem" tabindex="0">Open</div>
        <div role="menuitem" tabindex="-1">Save</div>
        <div role="menuitem" tabindex="-1">Close</div>
      </div>
    </template>
  8. Resolve `no-static-element-interactions` by adding ARIA roles

    main

    When an element acts as a specific interactive component (like a button or link) but is a static element, you must:

    1. Add a role attribute to define its semantic purpose.
    2. Add tabIndex="0" to ensure the element is focusable.
    3. Implement the necessary keyboard event handlers (e.g., @keypress) to match the expected behavior of that role.

    Warning: Adding a role only provides semantics; it does not add functional behavior. Unlike a native <button>, a <div> with role="button" will not automatically respond to the Enter or Space keys. You must manually implement that logic.

    Note: Do not use role="presentation" on interactive elements, as this removes the semantics required for accessibility.

    <template>
      <div
        @click="onClickHandler"
        @keypress="onKeyPressHandler"
        role="button"
        tabIndex="0"
      >
        Save
      </div>
    </template>
  9. Use the aria-role rule to validate ARIA roles

    main

    The aria-role rule ensures that any element using an ARIA role attribute uses a valid, non-abstract ARIA role. This prevents developers from using non-existent roles (like datepicker) or abstract roles (like range) that are intended to be extended rather than used directly on DOM elements.

    Roles that are determined at runtime via dynamic bindings (e.g., :role="roleVariable") are ignored by this rule because their validity cannot be determined during linting.

    <template>
      <div role="button" />
      <!-- Good: "button" is a valid ARIA role -->
    
      <div :role="role" />
      <!-- Good: role is a variable & cannot be determined until runtime. -->
    
      <div role="datepicker" />
      <!-- Bad: "datepicker" is not an ARIA role -->
    
      <div role="range" />
      <!-- Bad: "range" is an _abstract_ ARIA role -->
    </template>
  10. Avoid redundant ARIA roles with no-redundant-roles

    main

    The no-redundant-roles rule prevents you from adding ARIA roles to HTML elements that already have those roles implemented natively by the browser. Adding an implicit role that matches the element's default behavior is redundant and can clutter the accessibility tree.

    Examples of redundant roles:

    • <button role="button"> (The button element already has the button role).
    • <img role="img"> (The img element already has the img role).

    Examples of valid usage:

    • <button role="presentation"> (Changing the semantic meaning to be non-interactive).
    • <div /> (No role applied).
    <!-- Fail -->
    <button role="button" />
    <img role="img" src="foo.jpg" />
    
    <!-- Succeed -->
    <button role="presentation" />