Improving Accessibility with Custom ember-template-lint Rules

Last reviewed on January 28, 2022

Recently I was working in an Ember application where there were no links. Instead of links, there were buttons with click handlers that invoked a function which did a transition. For example:

<button {{on "click" (fn this.nav.toRoute "profile")}}>  Profile
</button>

The reasoning behind this implementation was so that this.nav.toRoute could perform an animation between page transitions. However, this came at the expense of accessibility.

Once I figured out how to address the animation without this.nav.toRoute, I wanted to ensure that links were used instead of buttons with click handlers that simply performed a transition. To do this, I wrote a custom ember-template-lint rule to encourage using links as follows:

lib/template-lint-rules/no-to-route.js
const { Rule } = require("ember-template-lint");

module.exports = class extends Rule {
  visitor() {
    return {
      PathExpression(node) {
        if (node.original === "this.nav.toRoute") {
          const { line, column } = node.loc.start;
          const { line: endLine, column: endColumn } = node.loc.start;

          this.log({
            message:
              "Found this.nav.toRoute in template. Use <LinkTo> instead for better accessibility.",
            line,
            column,
            endLine,
            endColumn,
            source: this.sourceForNode(node),
          });
        }
      },
    };
  }
};

If you've never written a custom ember-template-lint rule, I recommend going through the Abstract Syntax Forestry workshop by Simplabs for EmberConf2020.

To enable this lint rule, I updated the ember-template-lint configuration file as follows:

.template-lintrc.js
const noToRoute = require("./lib/template-lint-rules/no-to-route");

module.exports = {
  extends: "recommended",
  plugins: [
    {
      name: "acme",
      rules: {
        "acme/no-to-route": noToRoute,
      },
    },
  ],
  rules: {
    "acme/no-to-route": "error",
  },
};

Lastly, I used ember-template-lint's TODO feature to temporarily ignore the lint errors. Even though these lint errors were temporarily ignored, each infraction showed a red underline underneath it in VS Code to signal to other developers not to continue using it.