Improve your Vue 3 accessiblity with linters & git hooks
Tags: vue, a11y, javascript, git
Published: 2022-11-13 14:43:42

💖 Originally published on [my blog](https://lisilinhart.info/posts/improve-your-vue-3-accessiblity-with-linters-git-hooks)

In this post, we'll look into adding accessibility linters to your Vue 3 project. This can be your regular Vue 3 app or a Nuxt 3 website or any other environment. Adding accessibility linters can be a great first step to improving the accessibility of your app or website. Of course, you should always learn about different accessibility topics and there is no one size fits all solution, but linters can be a great baseline because they are enforced while you're writing you're code. With git hooks we can ensure that no new code is added, that doesn't follow these linters and it can also be useful for junior developers to learn when they might be doing something wrong. In this post we'll look into adding the following tools & linters:

- [eslint-plugin-vuejs-accessibility](https://www.npmjs.com/package/eslint-plugin-vuejs-accessibility)
- [@ronilaukkarinen/stylelint-a11y](https://www.npmjs.com/package/@ronilaukkarinen/stylelint-a11y)
- [lint-staged](https://www.npmjs.com/package/lint-staged)

## Creating a new Vue 3 app

First, we'll create a new Vue 3 app. The [Vue quickstart](https://vuejs.org/guide/quick-start.html) makes it very easy to create a new project.

```
npm init vue@latest
```

Make sure to directly install the ESLint and Prettier plugins, because they will help us later on.

```
✔ Add ESLint for code quality? - Yes
✔ Add Prettier for code formatting? - Yes
```

Now we can directly jump into our new project and it should already show the welcome screen.

```
cd vue-project
npm install
npm run dev
```

## Adding the ESLint accessibility plugin

The next step is to extend our ESLint setup to include a plugin for a11y. Since we scaffolded our project with ESLint, we should have a `.eslintrc.cjs` file in our project with some standard plugins installed. Let's take a look at it:

```
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')

module.exports = {
  root: true, // This is required to prevent ESLint from looking for a configuration file in parent folders
  'extends': [
    'plugin:vue/vue3-essential', // vue 3 linting
    'eslint:recommended', // standard eslint javascript linting
    '@vue/eslint-config-typescript', // typescript linting
    '@vue/eslint-config-prettier' // prettier linting
  ],
  parserOptions: {
    ecmaVersion: 'latest' // Allows for the parsing of modern ECMAScript features
  }
}
```

So let's add the [eslint-plugin-vuejs-accessibility](https://www.npmjs.com/package/eslint-plugin-vuejs-accessibility) plugin to our project. We can do this by running the following command:

```
npm install --save-dev eslint-plugin-vuejs-accessibility
```

And then we need to add it to our `.eslintrc.cjs` file:

```
module.exports = {
  root: true,
  'plugins': ['vuejs-accessibility'], // add the plugin
  'extends': [
    "plugin:vuejs-accessibility/recommended", // add the recommended rules
    ...
  ],
  parserOptions: { ... }
}
```

After this, we can run our linter and see if we have any accessibility issues in our project. Since we just scaffolded the project there are probably no issues. We can run the linter with the following command:

```
npm run lint
```

Perfect, no issues! Let's add some accessibility issues to our project and see if the linter can catch them. Open your `App.vue` file and remove the `alt` attribute from the image tag. The linter should now show an error:

```
<header>
    <img
        alt="Vue logo" // <--- remove this line
        class="logo"
        src="./assets/logo.svg"
        width="125"
        height="125"
    />

    <div class="wrapper">
        <HelloWorld msg="You did it!" />
    </div>
</header>
```

When running `npm run lint` again, we should see the following error:

```
✖ error  img elements must have an alt prop, either with meaningful text, or an empty string for decorative images  vuejs-accessibility/alt-text
```

We can see more details for the specific rules in the docs of the different plugin rules like this one: [vuejs-accessibility/alt-text](https://github.com/vue-a11y/eslint-plugin-vuejs-accessibility/blob/main/docs/alt-text.md)

### Adapting the linter

It's also possible that you need to activate a specific rule or set options for a rule, because your project has specific needs. You can do this by adding a `rules` section to your `.eslintrc.cjs` file:

```
module.exports = {
  root: true,
  'plugins': ['vuejs-accessibility'],
  'extends': [
    "plugin:vuejs-accessibility/recommended",
    ...
  ],
  'rules': {
    'vuejs-accessibility/alt-text': ['error', { // <--- this would change the options of the rule
      'elements': ['img'], // <--- e.g. only lint images, but no other HTML elements
      'img': [],
      'object': [],
      'area': [],
      'input[type="image"]': []
    }],
    "vuejs-accessibility/no-autofocus": "off", // <--- this would disable this rule
  },
  parserOptions: { ... }
}
```

## Adding stylelint and the stylelint-a11y plugin

Next we also want to lint our CSS, because we can also have accessibility issues there. We can do this by adding the [stylelint](https://stylelint.io/) linter to our project. The configuration there depends on what CSS processing you're using. For this case, we'll use CSS modules:

```
npm install --save-dev postcss-html stylelint stylelint-config-recommended-vue stylelint-config-standard stylelint-config-prettier  stylelint-config-css-modules @ronilaukkarinen/stylelint-a11y
```

Let's take a look at these packages:

- [stylelint](https://www.npmjs.com/package/stylelint) - The core linter
- [stylelint-config-standard](https://www.npmjs.com/package/stylelint-config-standard) - The standard config for stylelint
- [stylelint-config-prettier](https://www.npmjs.com/package/stylelint-config-prettier) - The config to make sure that the stylelint and prettier configs work together
- [stylelint-config-recommended-vue](https://www.npmjs.com/package/stylelint-config-recommended-vue) - The config for linting Vue components
- [@ronilaukkarinen/stylelint-a11y](https://www.npmjs.com/package/@ronilaukkarinen/stylelint-a11y) - The plugin for linting accessibility issues in CSS (this is a fork of the original plugin, but works for stylelint version >= 14)

### Adapting the stylelint configuration

After installing the packages, we can create a new `.stylelintrc.js` file in the root of our project and add the following config:

```
module.exports = {
  extends: [
    "stylelint-config-standard",
    "stylelint-config-prettier",
    "stylelint-config-recommended-vue",
    "stylelint-config-css-modules",
    "@ronilaukkarinen/stylelint-a11y/recommended",
  ],
};
```

Let's also add a new script to our `package.json` file to run the linter. By adding `--fix` in the end, it would try to autofix the issues:

```
{
  "scripts": {
     "lint:style": "stylelint 'src/**/*.{vue,css}' --fix" 
  }
}
```

Now we can run stylelint to see if we have any issues in our CSS:

```
npm run lint:style
```

When running `npm run lint:styles` again, we should already see an error:

```
✖  Unexpected duplicate selector ":root", first used at line 2  no-duplicate-selectors
```

The stylelint plugin only activates three rules by default, but we can enable [more rules](https://www.npmjs.com/package/@ronilaukkarinen/stylelint-a11y) by adapting the `.stylelintrc.js` file:

```
module.exports = {
    extends: [
        "stylelint-config-standard",
        "stylelint-config-prettier",
        "stylelint-config-recommended-vue",
        "stylelint-config-css-modules",
        "@ronilaukkarinen/stylelint-a11y/recommended",
    ],
    rules: {
        "a11y/no-text-align-justify": true,
        "a11y/font-size-is-readable": true,
        "a11y/media-prefers-color-scheme": true,
        "a11y/line-height-is-vertical-rhythmed": true,
        "a11y/no-display-none": true,
        "a11y/no-obsolete-element": true,
    },
};
```

Let's try breaking some of those rules. Open the `src/App.vue` file and add the following CSS:

```
.wrapper {
  line-height: 1.3;
  outline: none;
  text-align: justify;
  font-size: 12px;
}
```

This should trigger some more errors:

```
✖  Expected a larger font-size in .wrapper               a11y/font-size-is-readable
✖  Expected a vertical rhythmed line-height in .wrapper  a11y/line-height-is-vertical-rhythmed
```

## Adding a pre-commit hook to prevent committing code with accessibility issues

Now that we have a linter that checks our code for accessibility issues, we can add a pre-commit hook to prevent committing code with accessibility issues. We can do this by using the [lint-staged](https://www.npmjs.com/package/lint-staged) package and [husky](https://typicode.github.io/husky/#/). Let's install these packages:

```
npm install --save-dev lint-staged husky
```

### Adding lint-staged

We can add a new `lint-staged` config to our `package.json` file to run the linters on the staged files:

```
{
  "lint-staged": {
    "*.{js,ts,vue}": [
      "npm run lint"
    ],
    "*.{css,vue}": [
      "npm run lint:style"
    ]
  }
}
```

### Installing husky

Next we need to run `lint-staged` whenever we commit files. Husky is a tool that allows us to run scripts at certain git events. We can use it to run a script before we commit our code. The following command will setup husky, modify our `package.json` and create a sample pre-commit hook that you can edit. By default, it will run npm test when you commit.

```
git init # if you haven't initialized git yet
npx husky-init && npm install
```

After running the command, you should see a new `.husky` folder in the root of your project. In that folder, you'll find a `pre-commit` file. Let's edit that file and replace `npm test` with the following:

```
npx lint-staged
```

Now you can try commiting some code with accessibility issues and see that the commit fails.

```
git add .
git commit -m "test"
```

Lint staged should run the linters and prevent you from committing code with accessibility issues.

```
✔ Preparing lint-staged...
✔ Hiding unstaged changes to partially staged files...
❯ Running tasks for staged files...
  ❯ package.json — 31 files
    ❯ *.{js,ts,vue} — 14 files
      ✖ npm run lint [KILLED]
    ❯ *.{css,vue} — 11 files
      ✖ npm run lint:style [FAILED]
✔ Applying modifications from tasks...
↓ Skipped because of errors from tasks. [SKIPPED]
```

### Conclusion

In this article, we learned how to add accessibility linting to our Vue 3 project. By adding pre-commit hook we can prevent committing code with obvious accessibility issues. This is a perfect starting point for a new project, where multiple developers are working on the same codebase trying to make it accessible from the beginning. If you want to learn more about accessibility, here are some great resources & tools:

- https://www.a11yproject.com/checklist/
- https://www.w3.org/WAI/resources/
- https://www.deque.com/axe/
- https://wave.webaim.org/