(Programmatically) Creating a navigation menu

As I’m working in enhancing the templates for my essay collection I’ve come up with a need to have a menu to link to the essays I want to make available to readers but not all the writing in the location where these essays are available. Rather than manually creating the menu I’ve decided t hat it may be better to atomate the process and use JSON and Javascript to create it.

I’ve broken the content in three pieces, the JSON file with the items I want to add, the Javascript to create the menu and a small CSS to remove the bullet point from the menu items.

It this a perfect solution. No, I don’t think there is such a thing. But it is a good way to learn how to create HTML content based on external data.

JSON

The JSON file is an array of projects with each child having two items: a name and a URL. It has been validated with jsonlint to make sure that it’ll work properly.

If we wanted to enhance the menu we could add more information to individual items. For example, if every project has an image we could add it to the JSON and use it when building the list.

{
  "projects":
  [{
    "name": "Intersection Observers",
    "url": "intersection-observers.html"
  }, {
    "name": "HTML Video",
    "url": "html5-video.html"
  }, {
    "name": "Crazy Layouts",
    "url": "layouts.html"
  }, {
    "name": "Better Markdown From Node",
    "url": "better-markdown-from-node.html"
  }, {
    "name": "Book Proposal",
    "url": "book-proposal.html"
  }, {
    "name": "Crafting Reading Experiences",
    "url": "crafting-reading-experiences.html"
  }, {
    "name": "CSS and Javascript Working Together",
    "url": "css-and-javascript-working-together.html"
  }, {
    "name": "Define Easy",
    "url": "define-easy.html"
  }, {
    "name": "Digital Storytelling",
    "url": "digital-storytelling.html"
  }]

}

Javascript

I’ve brokent the Javascript into two blocks. The first one block is two utility functions to create nodes and append a child node to the parent. They are not strictly necessary but they make life easier and they can be easily reused.

function createNode(element) {
  // Create the type of element you pass in the parameters
  return document.createElement(element); 
}

function append(parent, el) {
    // Append the second parameter(element) to the first one
    return parent.appendChild(el); 
}

The second block is what actually builds the menu. The scripts has the following elements.

We first fetch the data and convert the response to JSON. We then assign the projects JSON array to a variable and grab a reference to the menu container.

Using array.map we look through the projects array and do the following:

  • Create a li element and attach a class of menu-item-link that we’ll use to style the element later
  • Create a link element a and attach the href element with a value of the current item’s url using a string template literal ${project.url}
  • Add the text of the link adding a string template literal ${project.name} to the innerHTML of the link element we created

If there is an error we log it to console and exit. Nothing we can really do in that situation.

fetch('menu-data.json') // 1
  // Transform the data into json
  .then(resp => resp.json()) // 2 
  .then(data => {
    let projects = data.projects; 
    let menu = document.getElementById('menu-container');

    return projects.map(project => { 
      let li = createNode('li');
      li.setAttribute('class', 'menu-item-link');

      // create the anchor
      let anchor = createNode('a');
      anchor.href = `${project.url}`;

      let liContent = `${project.name}`;
      anchor.innerHTML = liContent;

      append(li, anchor);
      append(menu, li);
    })
  })
  .catch(error => {
    console.log('there was a problem: ', error);
});

We could create the link as full string template literal and skip the node creation process. It may work like this:

return projects.map(project => { 
  let li = createNode('li');
  li.setAttribute('class', 'menu-item-link');

  // create the anchor
  let link = '<a href="`${project.url}`">`${project.name}`</a>';

  append(li, link);
  append(menu, li);
})

The problem with this version is support for older browsers. It will only work on modern browsers that support ES6 natively. If we want to work with older browsers we would then have to code an alternative version or use a polyfill.

CSS

In CSS there is only one rule. We hide the bullets on the list items. We use the class menu-item-link to make sure we only hide the bullets on the menu and move the content to the left so it’ll take the space we gained by removing the bullets.

.menu-item-link  {
  list-style: none;
  margin-left: -3em;
}

Working with text beyond English

Most of the time we don’t have to worry about alignment and text direction when working with web content. When the language works the same as English we have no problem. We start having issue becomes when we work with languages other than English in the same document. It gets more complicated when our primary language uses a different writing mode than the one we’re used to. Think about vertical Kanji or right-to-left Hebrew or Arabic text mixed with your traditional English or latin content.

We’ll discuss tools that CSS gives you to control how languages other than English appear on web pages. We’ll briefly talk about some CSS terminology that makes our CSS DRY by making multiple rules unnecessary.

Why is this important? People

Towards the end of his presentation at Fronteers 2016 Bruce Lawson states that lack of awareness and locally relevant content is the most common barrier to Internet adoption in Asia.

Later in Bruce’s presentation states that 50% of websites are in English, a language spoken by 10% of speakers in the survey countries. By way of contrast, only 2% of websites worldwide are in Mandarin and less than 0.1% in Hindi. (Data verified with information from: w3techs)

While English is the predominant language of the web it is not a universal language, particularly in emerging markets and rural areas of developed nations. There is also an assumption that the rules of western communication, possession of high end hardware and even cultural norms will be equally applicable across the world.

That is not true.

WWW is the World Wide Web not Wealthy Western Web

— Bruce Lawson

What would the web look in their language? What changes would we have to make if we want our content to be understood on countries where people don’t read English? Again, where are your customers coming from? is it where we expect them from?

Why is this important? Development

Most of us do our work in English but that may not always be the case. We might be called to create content in languages other than English or to mix content in English and other languages, some of which may be laid out differently than your main content.

We can use writing modes to play with the way our standard content is laid out on the page. Along with transitions we can rotate our text and make it look different than the standard left to right, top to bottom.

Look at this pen from Jen Simmons; pay special attention to the way she styled the h1 element and how she changed direction without actually having to use transitions or animations.

See the Pen Writing Mode Demo — Headline by Jen Simmons (@jensimmons) on CodePen.

The code that changes the direction of the h1 element is this:

h1 {
  writing-mode: vertical-rl;
}

The full list of values for writing-mode are:

h1 {
  writing-mode: horizontal-tb;
}

h1 {
  writing-mode: vertical-rl;
}

h1 {
  writing-mode: vertical-lr;
}

h1 {
  writing-mode: sideways-rl;
}

h1 {
  writing-mode: sideways-lr;
}

Understanding writing modes may help you better understand Grid and Flexbox. Did you ever wonder why Flexbox attributes use start and end in their names? By doing so the layout will work the same regardless of where the start and end of our content are.

Now that we know what we’re working we need to define a few more concepts before we can look at different writing systems and how we can use them on the web.

Three concepts to understand when working with writting modes
  • block direction
  • inline direction
  • character direction

Block direction

Block direction describes how blocks of text are laid out, whether it’s horizontally or vertically. Most languages wil use a vertical block direction but not all of them.

Inline direction

Inline direction controls the way the text flows, left to right or right to left… yes, even in vertical layouts. The dir global HTML attribute controls the inline direction of the content. That’s why you see (or should see) html tags like this for United States English:

<html lang='en-us' dir='ltr'>

And like this for Chilean Spanish:

</html><html lang='es-cl' dir='ltr'>

Character direction

While studying this, and reading lots of articles by Jen Simmons, I found Character direction, the most confusing. It describes where the top of a character is.

For example: if you write a capital Y, where is the top of the character? Most languages will use top to bottom but not all.

Different writing systems

There are four main writing systems. We’ll discuss them along with any changes we need to make to our CSS to acommodate for their differences.

Latin based systems

Most languages in the world will use a Latin based writing system with top to bottom and left to right. This is also the default for CSS so we don’t need to set it as the default.

When working with multiple languages in the same document we can reset it with something like this”

div .english {
  writing-mode: horizontal-tb;
}

Arabic-based systems

Arabic-based systems (including Arabic and Henrew) that use a top to bottom, right to left system (also known as RTL).

technically Arabic systems have a top to bottom blockdirection and a right to let inline direction.

Take the following example website for the united nations in Eglish and Arabic and see how the text is laid out on the Arabic site (figure 2), both the text and the layout read from right to left and look like a mirror of the English site.

United Nations site in English
United Nations site in Arabic

To use Arabic as the default language for a page, use the following html element:

<html lang='ar' dir='rtl'>

to control the direction of specific elements within the pge you can use the direction CSS attribute, like this:

.arabic-content {
  direction: rtl;  /* Right to Left */
}

Han based systems

Han based systems include, among others, the CKJ languages (Chinese, Korean and Japanese). The interesting part is that these languages can be written with vertical or horizontal display blocks.

If you write your Japanese in a top to bottom, left to right mode then you don’t have to make changes. The default writing mode will work.

If you write your content in a vertical layout you can use the following CSS:

html {
  writing-mode: vertical-rl;
}

And let the browser handle the content layout for you.

You can mix mix vertical and horizonal layouts in the same page like in this page from Vogue Japan:

Vogue Japan page using vertical and horizontal layouts

You can set the writing mode for the vertical portion of the article and let the rest use the default horizontal, left to right layout.

.articletext {
  writing-mode: vertical-rl;
}

Or you can change the default to a vertical orientation, and then set specific elements to horizontal-tb, like this:

html { 
  writing-mode: vertical-rl;
}

h2, .photocaptions, section {
  writing-mode: horizontal-tb;
}

If you mix western and CJK languages. Look for the English words in the Japanese text below:

Example of vertical Japanse and English text

Mongolian writing systems

OK, this was beyond what I expected but not too complex not to use it. There is one more writing system to add to the mix. We’ll probably never use it… it’s Mongolian.

Text in Mongolian runs vertically down the page just like Han-based systems. There differences are two:

  • Block-level elements stack from left to right
  • The character direction is “upside down”. The top of the Mongolian characters point to the right

Creative uses

We can use vertical writing directions to create headings like the Octavia Buttler example we discussed earlier. You can use it anyway you want to.

Figure as a universal container

I’ve been looking at some way to group content in a semantic way. For example, rather than using:

<div class="video">
<!-- iframe for youtube video -->
</div>

I would like to find an equivalent unit of content that would allow me to group the children and still be semantically valid. Looking at the code from Dudley Storey’s The New Code that I saw his use of the figure element to do this. Knowing that Dudley would never do something that went against spec I thought I’d look at figure and see if would do what I want it to do.

The figure element is surprisingly flexible in what it can hold. According to the HTML specification:

The figure element represents some flow content, optionally with a caption, that is self-contained (like a complete sentence) and is typically referenced as a single unit from the main flow of the document.

Self-contained in this context does not necessarily mean independent. For example, each sentence in a paragraph is self-contained; an image that is part of a sentence would be inappropriate for figure, but an entire sentence made of images would be fitting.

The element can thus be used to annotate illustrations, diagrams, photos, code listings, etc.

From the HTML5 specification

This means that we can go way beyond just doing the standard figure and image setup:

<figure>
  <img src='path/to/my/image.png' alt=''>
  <figcaption>Short description</figcaption>
</figure>

We can also do something like this:

<figure class='video'>
  <iframe width="560" height="315" 
    src="https://www.youtube.com/embed/rn7szaphdWk" 
    frameborder="0" allowfullscreen></iframe>
</figure>

Captions, generated text and other uses

figcaption is used to describe the element. If we go back to the image example:

<figure>
  <img src='path/to/my/image.png' alt=''>
  <figcaption>Short description</figcaption>
</figure>

We can use CSS generated content to tailor the way the figcaption look and add generated text to make sure that each image is idenitfiable on its own with a figure number before the text:

figure {
  counter-increment: figure-count;
  max-width: 100%;

  img {
    max-height: auto;
    max-width: inherit; 
  }

  figcaption {
    font-size: .75rem;
    color: rgb(51, 51, 51);
  }

  figcaption::before {
    content: 'Figure ' counter(figure-count) ': ';
  }
}

The two key elements to create self increasing counters are:

The counter-increment rule in the figure element increases the figure-count counter for every image that appears in the page.

The figcaption::before pseudo element that will insert the text, along with thecurrent value of the figure-count counter.

If we insert new images or delete existing ones the values will adjust automatically without us having to do any manual work, the CSS rules will handle the numbering of the figures. We could do the same thing for videos and any other elements that

We can also use figcaption to describe or otherwise enhance the content of the figure. In the video example we could use figcaption to hold buttons for a custom player, transcript display and other enhancements we choose to make to the video. Below is how we could write the markup for the custom buttons:

<figure class='video'>
  <iframe  id="video" width="560" height="315" 
  src="https://www.youtube.com/embed/rn7szaphdWk" 
  frameborder="0" allowfullscreen></iframe>
  <figcaption>
    <img src="path/to/images/seekBack.svg" 
      alt="seek back 15" id="back">
    <img src="path/to/images/play.svg" 
      alt="play / pause" id="play">
    <img src="path/to/images/seekForward.svg" 
      alt="seek forward 15" id="back">
  </figcaption>
</figure>

and then script the buttons to actually work with the video.

There are more things you can do with figures. To make sure your code still validates as HTML look at the spec for the figure and figcaption elements to make sure that the content you want to put inside the figure or caption is valid.

Async functions: better async?

New in ES2017 are async functions and the await keyword that will make writing async code easier to read, reason through and understand what caused any error that may get thrown. The hardest part, for me, of working with ES2016 and later is that I don’t always see the reasoning behind the new code, the older version of the code still work just as fine.

Async / Await are different. They look a lot like the callback code that we used to work with in the ES5 days but they produce the same asynchronous result as if we were writing promises. It is very similar to how we’d write asynchronous code when using generators either natively or with a library like co

Async code running sequentially

Take the following code that represents sequential asynchronous calls

async function asyncFunc() {
    const result1 = await otherAsyncFunc1();
    console.log(result1);
    const result2 = await otherAsyncFunc2();
    console.log(result2);
}

And compare it with the code that produces the same result using promises:

function asyncFunc() {
    return otherAsyncFunc1()
    .then(result1 => {
        console.log(result1);
        return otherAsyncFunc2();
    })
    .then(result2 => {
        console.log(result2);
    });
}

As you can see the main difference is that await takes place of the then block. The code is cleaner and it makes more sense to me (not that the promise code is hard to read, just not as clean).

Async code running in parallel

The code works and it’s cleaner but it’s sequential. The await statements run sequentially and will wait for one promise to return before executing the next. There are times when we want to run all our promises in parallel either because we want the code to run fast or because we have enough promises that running them sequentially would slow the code execution too much.

To run promises in parallel we use Promise.all. Just like in promise based code we build an promise to an array that will fulfill if all promises succeed or fail if anyone of t hose promises fail.

Here is the async / await code to log the result of two promises.

async function asyncFunc() {
    const [result1, result2] = await Promise.all([
        otherAsyncFunc1(),
        otherAsyncFunc2(),
    ]);
    console.log(result1, result2);
}

With the corresponding promise based code. See how similar the two are?

function asyncFunc() {
    return Promise.all([
        otherAsyncFunc1(),
        otherAsyncFunc2(),
    ])
    .then((result1, result2) => {
        console.log(result1, result2);
    });
}

Error handling

The final part of the equation is how to handle errors. To me this was the most surprising part of the exercise, going back to using try / catch blocks to handle errors just like the old synchronouse code we used to write, except that it’s running the code sequentially and waits for each task to complete before performing the next.

async function fetchJson(url) {
    try {
        let request = await fetch(url);
        let text = await request.text();
        return JSON.parse(text);
    }
    catch (error) {
        console.log(`ERROR: ${error.stack}`);
    }
}

Recreating the font loader script with async and await

A few weeks ago I wrote a script to use Font Face Observer to make sure that readers got a consistent reading experience and that I could, as much as possible, control font behavior in my pages. The full script is shown below:

const mono = new FontFaceObserver('notomono-regular');
const sans = new FontFaceObserver('notosans-regular');
const italic = new FontFaceObserver('notosans-italics');
const bold = new FontFaceObserver('notosans-bold');
const bolditalic = new FontFaceObserver('notosans-bolditalic');

let html = document.documentElement;

html.classList.add('fonts-loading');

Promise.all([
  mono.load(),
  sans.load(),
  italic.load(),
  bolditalic.load()
]).then(() => {
  html.classList.remove('fonts-loading');
  html.classList.add('fonts-loaded');
  console.log('All fonts have loaded.');
}).catch(() =>{
  html.classList.remove('fonts-loading');
  html.classList.add('fonts-failed');
  console.log('One or more fonts failed to load')
});

A version of the script using async and await may look like this. Notice how we use try and catch blocks to control the process of our script.

const mono = new FontFaceObserver('notomono-regular');
const sans = new FontFaceObserver('notosans-regular');
const italic = new FontFaceObserver('notosans-italics');
const bold = new FontFaceObserver('notosans-bold');
const bolditalic = new FontFaceObserver('notosans-bolditalic');

let html = document.documentElement;

html.classList.add('fonts-loading');

async function loadFonts() {
  try {
    const results = await Promise.all([
      mono.load(),
      sans.load(),
      italic.load(),
      bold.load(),
      bolditalic.load()
    ]);
    html.classList.remove('fonts-loading');
    html.classList.add('fonts-loaded');
    console.log('All fonts have loaded.');
    return results;
  }
  catch (error) {
    html.classList.remove('fonts-loading');
    html.classList.add('fonts-failed');
    console.log('One or more fonts failed to load')
  }
}

Async functions and the await keyword are fully supported in modern browsers but not in older versions. How to handle the difference between supported and non supported browsers? We can use feature detection to work the promise code and break early if promises are supported.

Or we can choose not to care about older browsers and support only current browsers that will work with the features we want.

Which one you use is up to you.

Links and References

Incorporating accessibility evaluation into your build process

I have to admit that I don’t pay as much attention to accessibility as I should. I don’t have enough of an audience to merit warranting the extra work that installing accessibility tools and running the tests periodically entail. But what if I could run the accessibility tests every time I run my build process? Then it would only be an issue of acting on the accessibility suggestions and that’s a lot easier than running the tests.

While at Smashing Conference I talked to one of the presenters and discussed the possibility of building a skeleton build system that contained some of the most often used task, and accessibility and a way to test if your gulp plugins are out of date or present a security risk.

This post presents the accessibility part of the build system and shows how you could incorporate accessibility evaluation into your Grunt or Gulp based system. If you use other task runners or build systems that you’d want to see incorporated into this post or in an update please send me the code… I’ll be happy to add it.

What Tool?

I’ve chosen to work with AxeCore from Deque for a number of reasons:

  • It’s open source and the code is available on Github for review and extensions if needed
  • There are plugins for Gulp and Grunt
  • If needed there are also extensions for Firefox and Chrome
  • It’s actively supported and promoted by the Deque team

Strategy

The idea behind using a build system is to automate the tedious tasks involved in front-end development. Using Axe Core and gulp-axe-webdriver we add accessibility to the list of t hings we automate. One way to run the system would be like this:

  1. Build the web content
  2. Configure your build system to run Axe Core on the directories containing your build
  3. Review the report
  4. Make changes and run the report again as needed

What build system?

Build system selection is a touchy subject, almost up there with Mac versus PC and Coke versus Pepsi. I began using Grunt and later, because of Polymer, moved to Gulp. Fortunately Axe-core provides plugins for both.

If you’re using a different build system or task runner I’ll assume that you know what you’re doing or know where to get help with issues you may encounter.

Now here we go.

Gulp

Gulp is a stream-based build system and it’s easier to configure than Grunt and, possibly, others. If there is no plugin available you can run the tool directly from within a Gulp task.

Fortunately we don’t have to do that, there is a plugin (gulp-axe-webdriver) available to use. We’ll explore the process of configuring Gulp, adding gulp-axe-webdriver, creating a task with the plugin and running it to generate results.

Installation and configuration

This section assumes that you haven’t installed and configured Gulp before. We’ll work on a sample project to illustrate how this works. Note that the commands are geared towards OS X and Linux, where necessary I’ll add the Windows commands in a separate section.

Before proceeding make sure that you install Node from the Node website if you haven’t already. This installer provides Node itself and the NPM package manager.

// 1
mkdir axe-project
// 2
cd axe-project

// 3
npm init --yes

// 4
npm install -g gulp
// 5
npm install --save-dev gulp gulp-axe-webdriver
  1. Make a directory
  2. Change to the directory you just created
  3. Initialize an empty NPM repository using defaults automatically. This will create the project’s package.json file
  4. Install the Gulp package globally to make the gulp command globally available, this we can run gulp directly form the command line
  5. Install Gulp and gulp-axe-webdriver for the project as development dependencies (with the --save-dev parameter). This will write the files and versions to the package.json file

The gulpfile

const gulp = require('gulp');
const axe = require('gulp-axe-webdriver');

// AXE CORE A11Y Tests
gulp.task('axe', done => {
  let options = {
    saveOutputIn: './a11yReport.json',
    browser: 'phantomjs',
    urls: ['build/**/*.html']
  };
  return axe(options, done);
});

// The default task (called when you run `gulp` from cli)
gulp.task('default', ['axe']);

Grunt

This section assumes that you haven’t installed and configured Grunt before. We’ll work on a sample project to illustrate how this works. Note that the commands are geared towards OS X and Linux, where necessary I’ll add the Windows commands in a separate section.

Before proceeding make sure that you install Node from the Node website if you haven’t already. This installer provides Node itself and the NPM package manager.

Installation and configuration

// 1
mkdir axe-project
// 2
cd axe-project

// 3
npm init --yes

// 4
npm install -g grunt-cli
// 5
npm install --save-dev grunt grunt-axe-webdriver
  1. Make a directory
  2. Change to the directory you just created
  3. Initialize an empty NPM repository using defaults automatically. This will create the project’s package.json file
  4. Install the Gulp package globally to make the grunt command globally available, this we can run gulp directly form the command line
  5. Install Grunt and grunt-axe-webdriver for the project as development dependencies (with the --save-dev parameter). This will write the files and versions to the package.json file

The gruntfile

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    axe: {
      your_browser_target: {
        // Target-specific file lists and/or options go here.
        options: {
        },
        // The default value is an empty array. 
        // At least one URL needed to complete the task.
        urls: [],
        dest: "output.json",
        junitDest: "output.xml"
      },
    }
  });

  // Load the plugin that provides the "axe-webdriver" task.
  grunt.loadNpmTasks('grunt-axe-webdriver');

  // Default task(s).
  grunt.registerTask('default', ['axe']);

};

Reading and understanding the results

Both Gulp and Grunt produce JSON output that we can further process or read as it is.

[{
    "violations": [{
        "id": "button-name",
        "impact": "critical",
        "tags": [
            "wcag2a",
            "wcag412",
            "section508",
            "section508.22.a"
        ],
        "description": "Ensures buttons have discernible text",
        "help": "Buttons must have discernible text",
        "helpUrl": "https://dequeuniversity.com/rules/axe/2.1/button-name?application=webdriverjs",
        "nodes": [{
            "any": [{
        "id": "non-empty-if-present",
        "data": null,
        "relatedNodes": [],
        "impact": "critical",
        "message": "Element has a value attribute and the value attribute is empty"
                }, {
        "id": "non-empty-value",
        "data": null,
        "relatedNodes": [],
        "impact": "critical",
        "message": "Element has no value attribute or the value attribute is empty"
                }, {
        "id": "button-has-visible-text",
        "data": "",
        "relatedNodes": [],
        "impact": "critical",
        "message": "Element does not have inner text that is visible to screen readers"
                }, {
        "id": "aria-label",
        "data": null,
        "relatedNodes": [],
        "impact": "critical",
        "message": "aria-label attribute does not exist or is empty"
                }, {
                    "id": "aria-labelledby",
                    "data": null,
                    "relatedNodes": [],
                    "impact": "critical",
                    "message": "aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty or not visible"
                }, {
                    "id": "role-presentation",
                    "data": null,
                    "relatedNodes": [],
                    "impact": "moderate",
                    "message": "Element's default semantics were not overridden with role=\"presentation\""
                }, {
                    "id": "role-none",
                    "data": null,
                    "relatedNodes": [],
                    "impact": "moderate",
                    "message": "Element's default semantics were not overridden with role=\"none\""
                }
            ],
            "all": [],
            "none": [{
                "id": "focusable-no-name",
                "data": null,
                "relatedNodes": [],
                "impact": "serious",
                "message": "Element is in tab order and does not have accessible text"
            }],
            "impact": "critical",
            "html": "<button class=\"Header-button Header-navToggle\" data-action=\"toggle-sidebar\">",
            "target": [
                "#frame > .Frame-header > .Header > .Header-button.Header-navToggle"
            ]
        }]
    }]
}]      

This example addresses one of the Rules in Axe Core related to button’s text. As you can see the JSON is not easy to parse it without help. It’s a very good idea to convert the JSON file to HTML or another format that is easier to read and understand

The Axe Core project provides a set of Handlebars templates and tools to convert the JSON file into readable HTML but, sadly, it’s beyond my understanding of how to make it work right now. If interested the files are explained in the HTML Handlebars document. I’m working on figuring out how to process the JSON file to convert it to human readable HTML.

Why bother?

I can hear you saying “this is too much work” and “why should I be the one doing this?” The truth is that accessibility is everyone’s responsibility and automating the process in its earliest build phase makes it easier to solve the problem.

Is this the only solution… not, it isn’t. Your team may implement additional accessibility tools and evaluations but this is a good initial assessment and orientation for what fixes to make.