November 13, 2022
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:
First, we'll create a new Vue 3 app. The Vue quickstart 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
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 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
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: { ... }
}
Next we also want to lint our CSS, because we can also have accessibility issues there. We can do this by adding the stylelint 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:
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 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
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 package and husky. Let's install these packages:
npm install --save-dev lint-staged husky
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"
]
}
}
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]
In this article, we learned how to add accessibility linting to our Vue 3 project. By adding pre-commit hooks 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: