Lessons learned making accessible UI in cables.gl #934
TobyKLight
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Lessons learned making accessible UI in cables.gl
Introduction
Hi I'm Toby. I am a sighted, able-bodied person. I am not a web accessibility expert.
At this point (Nov 2024) I can not find much info on making cables.gl patches accessible.
I have tried to build my own accessible web app with cables, so I thought I would contribute what I've learnt and tried as a starting point for accessibility knowledge in this community.
If you are working on a commercial project or a project with major government funding, then I would urge you to invest in professional accessibility consultants, including co-design and testing with people who use accessible technology daily. And then perhaps share what you learned with the community? Then we can all get better at this.
Note
This post is about making interactive experiences that are made with cables.gl accessible. I will link to some feature requests for cables.gl. The goal of these requests is making it easier to make accessible experiences with cables.gl.
This is not, generally, about making the cables.gl editor itself accessible.
Part1: Background
1.1 Standing on the shoulders of giants
If you've never looked at the web accessibility topic before it's worth getting an overview first.
Here are a few places you could start:
https://www.w3.org/WAI/fundamentals/accessibility-intro/
https://webaim.org/intro/
https://www.chillybin.co/intro-web-accessibility/
I would highlight that accessibility is not just a one-and-done thing. It keeps evolving as users, browsers and accessible technology evolves.
1.2 Compliance
You should make your interactive experiences accessible so everyone can use them.
But the law may also say you have to do this.
I want to highlight a new law in Europe, the European Accessibility Act (EAA).
It comes into force June 2025.
It appears at first it only applies to certain kinds of products and services like banking and public transport. But if you look closer it's deeper than that.
The EAA lists e-commerce as an affected service. At least in the German implementation looks like means will actually affect every website that offers some kind of e-commerce.
Check these guidelines (in German) for their example of a hairdresser that has to make their booking website accessible because that's considered e-commerce, even though hairdressing is not listed as one of the products/services affected by the EAA.
https://www.bmas.de/DE/Service/Presse/Meldungen/2022/leitlinien-fuer-die-anwendung-des-barrierefreiheitsstaerkungsgesetzes.html
1.3 Technical difficulty
There is a lot of great information on the web about making accessible websites. Much of this focuses on 'simple', mostly vertical, websites, (which makes a lot of sense when you zoom out and ask how can information be delivered to as many people as possible).
Unfortunately the kind of interactive experiences that are made with cables.gl often fall into the 'advanced' category of web development. If you don't make websites already this is like jumping in the deep end of the pool. Which corresponds to some unavoidable technical difficulty.
To make accessible UI today you have to get your hands dirty with:
I will give a short overview on the technical concepts and where possible I've tried to cables-ify them in ops, but ultimately you do need to understand how what you make is published on a web page as HTML.
Definitely expect that you will be looking things up. I know I have to.
Also don't expect I got everything 100% right myself. As I said I'm not an expert, this is only my best attempt at how to apply the information I did find to cables.gl and enhancing the accessibility of canvases.
1.4 Why so much about screen readers?
I'm going to talk about screen readers a lot. They are only one assistive technology and you may ask yourself 'wait I'm using cables.gl to generate a purely visual artwork, why should I support screen readers?'
There are five good reasons
Challenge yourself to design experiences that work well with screen readers. It will make you a better designer, artist, coder.
1.5 Self testing with screen readers
If it's a serious project then at the least you should test with a desktop and mobile screen reader, in particular because voiceover on ios is an essential tool of people who are non-sighted and it has some quirks.
You can read about most popular screen reader br here
1.6 WCAG & ARIA
Two important acronyms we will run into are WCAG and ARIA
WCAG
WCAG stands for 'Web Content Accessibility Guidelines'
This is a list of accessibility requirements that covers specific and measurable things like contrast. The requirements can be achieved to three levels known as A, AA, and AAA or level 1, 2, 3. It's the most common set of accessibility requirements you see reference to.
This is the full document of the latest (2.2) WCAG guidelines:
https://www.w3.org/TR/WCAG22/
I also recommend this IBM accessibility requirements site.
It includes multiple requirement sets merged together, searchable and filterable.
https://www.ibm.com/able/requirements/requirements
The IBM toolkit also breaks down requirements for different project phases and disciplines. e.g this list of visual design requirements.
https://www.ibm.com/able/toolkit/design/visual
ARIA
ARIA stands for 'Accessible Rich Internet Applications Suite'.
It refers to the technical solutions and standards that actually enable accessibility on the web.
https://www.w3.org/WAI/standards-guidelines/aria/
In particular lots of relevant HTML attributes will be called "ARIA-x" or "ARIA-y".
Sometimes with and without the hyphen depending on their implementation.
The ARIA authoring practices guide (APG) is a great resource for implementing these.
https://www.w3.org/WAI/ARIA/apg/
In particular the pattern reference in the APG for how interactive elements are expected to behave is very valuable.
https://www.w3.org/WAI/ARIA/apg/patterns/button/
1.7 DOM and Accessibility Tree
We are accustomed to websites that are visually broken up into multiple panels. If you were going to read the website out loud where would you start? When would you read out the main content vs the sidebar content?
There is a defined order. The HTML is ultimately a list and the sidebar should definitively be either before or after the main content. This order of HTML elements is known as the 'Document Object Model' (DOM).
The DOM order is what matters to accessible technology like screen readers and keyboard navigation.
This order is also known as an accessibility tree.
See here for how to view the accessibility tree in chrome.
Wait, it's a tree now? I thought you just said HTML was a list?
So it's both at the same time. This 'tree' is achieved because HTML can have elements inside other elements (children inside parents). The tree can still be expanded to an entirely vertical list.
So although it's a tree the branches are numbered and there is a specific order you would read them if you were reading out the whole page.
An example of the tree concept is how you can have buttons that are HTML children of a dialog. This means both visually the buttons are drawn inside the dialog box but also that semantically a screen reader knows the buttons belong to the dialog box. Hence, if the dialog box is properly set up with ARIA role and a label, the screen reader can announce that you have currently selected the 'OK' button of the 'Save' dialog box.
1.8 <canvas> HTML tag
Cables.gl content is rendered into a <canvas> HTML tag.
Here is what I mean about we have to jump in the deep end, because lots of accessible guides already say "accessible websites should avoid using <canvas>" at all, and even the alt-behaviour of a <canvas> is not straightforward.
We just talked about importance of using the DOM tree properly to place elements semantically inside the parents they belong to.
There's an important exception here with the <canvas> tag: HTML elements that appear to be on top of your <canvas> do NOT go inside the <canvas> tag.
The inside of a <canvas> tag is intended only for alt content that would be displayed if the browser didn't support rendering the <canvas>.
HTML content that should appear on top of a <canvas> should go next to it in the DOM (share a parent) and have the same absolute positioning reference as the <canvas>.
More on these soon.
1.9 Semantic web
I just told you about ARIA and if you make any complicated interactive experience you will have to use it. But you will also see it often said it's better not to use ARIA if you don't have to.
One of the reasons is because standard HTML and CSS already contain semantic information that can be read by accessible technology and promotes useful default behaviour in browsers.
For example there is a 'button' role in the ARIA standard. We can use that to say any HTML element type is a button.
Note we also have to add the button behaviour .
But there is already a HTML tag for button, better to use this if you can. It also will appear in the tab order by default.
This is just an example. I'll go into more detail on all these concepts below.
Part2: First Steps and the simple Alt-solutions
2.1 Accessibility approach depends on context
The first step to making your content accessible is to zoom out and consider how your cables.gl canvas will be experienced as part of the larger context.
In most cases your cables.gl canvas is experienced inside a website. What makes sense for accessibility will depend on the context of how the entire website should be experienced.
There is a spectrum here that determines how complex your accessibility solution has to be.
At the simple end is if your cables content will be pure decoration. That means to a viewer of the site your canvas only provides some visual candy and does not contribute to the content or knowledge imparted. In this case we don't actually make the cables content accessible, instead it should be hidden and not announced by accessible technology.
Close to this simple end of the spectrum is providing alt-content. A good usecase would be if your cables canvas provides a fancy animated data-vis graph, but theoretically a still image could have done the same job. Again we don't try to make the cables content accessible, instead we provide alt-content to be read out by screen readers (This is the same approach as alt-text for regular images).
In the middle of the complexity spectrum is the standard approach of making your UI accessible to assistive technology. This should be your minimum if you provide an immersive interactive experience that is critical to conveying the content of the site, like an explorable data visualisation. Then people using assistive technologies can also explore the dataset / play the game / discover all the content.
Further towards the complex end of the spectrum you may need to go beyond just making your UI navigable by assistive technology. You may need to add new features. For example you could provide a dynamic text description of visual content. This has benefits beyond just screen reader users, there can be sighted users who don't know what to make of your particle visualisation and would benefit from a text description option.
The most extreme complexity would be that you create a bespoke fully accessible experience without using any of the standard web accessibility approaches. This is possible, you could prevent the need for the screen reader and have an application that is fully voiced. You could create custom key interactions that make sense in your application (for example movement of an avatar in a game). I haven't gone this far so I will only touch on this at the end of this guide. If you go down this path I think you would definitely need the support of accessibility consultants who could help you understand what people who use assistive technology expect from these experiences. In particular how to signal that you are transitioning from a standard web experience to your custom solution, and how to make a good tutorial that will onboard all users properly.
All that being said, lets start with the simple approaches.
✨ 2.2 How to mark your canvas as decorative
If your cables.gl canvas is purely decorative then you should hide it from assistive technology.
What does decorative mean? see the WAI advice here.
[Here is an example of an embedded decorative cables.gl patch.]
(https://www.tobyk.com.au/cablestest/DecorativeCanvasExample/)
If you check this with a screen reader like NVDA or iphone voiceover it should not announce the canvas at all.
To treat your cables.gl patch as decorative:
1) When embedding the content hide the canvas itself from assistive technology by setting aria-hidden to true on the canvas element. (Note currently you have to do this in the HTML itself, it's not a setting in cables.gl)
example HTML code.
<canvas id="glcanvas" width="300" height="300" tabindex="-1" aria-hidden="true"></canvas>
2) Ensure any HTML elements you use in your cables patch have the aria-hidden attribute set to true, or these would confusingly also be discovered by screen readers. Use the SetAriaHidden op from AccessExt team for this.
example cables patch
https://cables.gl/p/CeSkdq
References
This technique aims to achieve the same result with <canvas> as WAI recommends for decorative <img> elements. For more detail see:
https://www.w3.org/WAI/tutorials/images/decorative/
https://www.w3.org/TR/WCAG22/#non-text-content
[More info on aria-hidden attribute] (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-hidden)
Notes
Why use aria-hidden? Theoretically just leaving blank content inside the canvas tag should be equivalent to an empty alt text on an img tag (alt=""), as the role of content inside these tags on a <canvas> is to provide alt content. However in this case NVDA will announce that the canvas exists as 'graphic'. I guess because almost every canvas on the web would have this blank alt content by default. Web authors who add alt="" to an img show intention to mark the image as decorative.
This is why I think using aria-hidden attribute is the better approach for hiding a canvas from assistive technology.
[Feature Request to make setting aria-hidden for a canvas a simple checkbox in patch settings] (#929)
✨ 2.3 How to provide alternative text for a canvas
If your cables.gl canvas visualises content that can also be explained through relatively short text, then you should provide alt text. You should also provide alt-text if the cables canvas contains short fixed text that is not visible to assistive technology (e.g using the TextMesh op, see below in part 3 for more info).
[For more information see this WAI guide on alt-text approaches with images.] (https://www.w3.org/WAI/tutorials/images/decision-tree/)
If you've set alt-text before with HTML images you would be familiar with the alt attribute.
The <canvas> HTML element is a bit different, it does not support the alt attribute. Instead the HTML content inside the <canvas> start and closing tag is treated as alt content. This content is not normally displayed, it is only available as alt content to accessible technology or as fallback content if the browser does not support showing the canvas.
Example of an embedded cables patch with alt text.
https://www.tobyk.com.au/cablestest/AltTextCanvasExample/
If you check this with a screen reader like NVDA or iphone voiceover it should announce the alternative text when it gets to the canvas.
To provide alt text content for your cables.gl patch:
1) Provide the alt text in between the <canvas> opening and </canvas> closing tag where you embed the canvas in your site.
example HTML code.
<canvas id="glcanvas" width="300" height="300" tabindex="-1"> A graph showing the amount of money donated to political parties. The red and blue party both received 10 million dollars while the green party received 1 million dollars. </canvas>
2) Ensure any HTML elements you use in your cables patch have the aria-hidden attribute set to true, or these would confusingly also be discovered by screen readers. Use the SetAriaHidden op from AccessExt team for this.
example cables patch
https://cables.gl/p/CeSkdq
The alt text content itself should replace the image functionally. I.e don't just describe that it's a graph, tells the screen reader user the information that the graph would usually convey to a sighted user.
If you can't explain simply what the <canvas> would tell a sighted user than either
a) Explain in greater detail in the main text of the webpage. [See the WAI guide on accessible complex images] (https://www.w3.org/WAI/tutorials/images/complex/)
b) Make your cables.gl interactively explorable for users of assistive technology. Keep on reading this guide.
References
This technique aims to achieve the same result with <canvas> as WAI recommends for <img> element alt-text. For more detail see:
https://www.w3.org/WAI/tutorials/images/decision-tree/
https://www.w3.org/TR/WCAG22/#non-text-content
Misc
Here is a Feature Request to set the alt-text from cables.gl patch settings
✨ 2.4 Setting Page titles
This isn't cables.gl specific but just a reminder if you are making a web experience.
Always ensure your page titles are accessible. That is the first thing read out by screen readers when your page loads and it's the title that will be saved in bookmarks by default, so the title should give a good idea what users can find on your page.
Some great info here on how to do that from AIM
✨ 2.5 How to test your cables patch with assistive technology.
I don't claim to have comprehensive testing but here is how you can start.
First off the assistive technology side.
I tested for desktop using NVDA + Chrome on windows.
NVDA can be downloaded from here.
Give them a donation if you can afford it.
I tested for mobile using iphone voiceover and safari.
Now for the cables.gl side.
Best of course is if you actually export and embed your patch as it will really be displayed.
Naturally you will want to check it more rapidly during development.
As of current writing (oct24) it doesn't work particularly well to use screen readers or keyboard in the editor view of your patch or the remote viewer.
The best way to quickly test depends on if your canvas will finally be part of a flow of other HTML content, or a fullscreen experience on its own.
Intermission: What about the sidebar?
Cables.gl comes with a convenient Sidebar set of ops for quickly providing end users with an interface to control a patch.
It would be nice if the sidebar was accessible as an easy solution for providing some basic accessibility, plus it means that heaps of existing cables.gl patches using the sidebar would become accessible.
As of current writing (Oct24) how accessible is the sidebar?
Not very. Only some of the widgets are focusable and operable with keyboard, and those that are focusable do not read out useful labels.
Currently the best widget is probably the slider. I can press tab to highlight a slider and even press left and right to change the value. But there is no visible focus highlight and the screen reader does not announce what the slider actually does (despite there being a label field on the slider)
[Here is a feature request to improve the accessibility of the sidebar.]
(#930)
Part3: Making your cables.gl interactive experience accessible
Ok if you got this far we exhausted the simple stuff. It's time to get our hands dirty and deep dive on how to make cables.gl patches with custom UI accessible.
3.1 Canvas and HTML elements
Zooming out again for a moment, note that cables.gl can produce two kinds of content:
A) GPU content. The <canvas> is an area on a webpage where a web app is basically free to draw pixels using (almost) the full graphics capability of your device. This is facilitated by frameworks like webgl and webgpu that are very similar to how native video games talk to graphics processors (GPUs). These days you don't actually need an embedded GPU in your device to view this content but for simplicity I'll just call this GPU content.
This content is generally not accessible to assistive technology like screen readers. The instructions sent to the GPU are along the lines of rectangles and triangles and low level material properties. Even if it was possible for accessible technology to read that data it's not semantically useful. It doesn't say "This is a button" "This is a hyperlink" etc.
This means the content of GPU text ops like TextMesh are not visible to screen readers.
B) HTML content. Cables.gl can also dynamically produce HTML elements, the same as regular webpages are made of. These can be styled with CSS. HTML can be pushed further for some interesting visual effects, but compared to GPU content they are ultimately limited graphically. HTML elements are generally considered suitable for making 2D graphical UI.
HTML elements are accessible to assistive technology by default and can be made semantically rich. So they can say "This is a form" "This is the submit button of the form" etc.
3.1.1 Combining Canvas and HTML elements
As you may have already guessed, we have to combine GPU content and HTML content to make accessible experiences. This can be a completely separate approach with a HTML control panel on side. But this content can also be combined so the user clicks or interacts with GPU elements overlaid with accessible HTML element interaction.
There is an important detail.
Remember before I said that the only purpose of child content of a <canvas> tag is alt content?
This means that **the HTML content that cables.gl creates is actually outside of the <canvas>, even though it visually appears to be on top of it. **
To make this work correctly you need to understand the DOM.
3.1.2 Important of the DOM order for accessibility
We introduced the Document Object Model (DOM) in part 1. It basically means the HTML structure of a web page.
There are two reasons we need to be on top of the DOM representation of our HTML content.
3.1.3 Where does cables.gl add new HTML elements in the DOM by default?
By default cables.gl appends new HTML elements at the end of the parent container of the </canvas>. See here for an example of default HTML element adding behaviour.
This default may be ok if you have only one HTML element on a fullscreen app.
But if you have multiple HTML elements generated by your cables.gl patch you should define their reading order anyway, so you will have to get your hands dirty with the DOM.
✨ 3.1.4 How to view the DOM and accessibility tree of cables.gl patch
The first step in working with the DOM is being able to view it.
In chrome you can use the inspector.
See here for how to view the DOM in Chrome
See here for how to view the accessibility tree in chrome.
Note that in most views of a patch you get on the cables.gl website, including in the editor, that the patch is contained in an iframe. This means you have to dig a bit to uncover it in the DOM. To find it quicker search in the DOM for 'glcanvas' as the <canvas> should have this as a unique id.
To search the DOM in Chrome on windows:
✨ 3.1.5 How to set DOM order of HTML content in cables.gl
Use the ElementChilds op to set the DOM order of HTML elements.
See an example of using the ElementChilds op here.
Check the result with a keyboard (press TAB) or screen reader here.
Note
It's not recommended to use multiple AppendChild ops to establish DOM order.
This is because if your HTML element is recreated it will need to retrigger the downstream AppendChild op. If you have multiple AppendChild ops and only one retriggers then the new child will be appended out of order.
Using the ElementChilds op instead maintains order.
✨ 3.1.6 How to align HTML and GPU content visually in cables.gl
To visually align cables HTML and GPU content we need to ensure generated HTML elements:
1) Give HTML elements position:absolute property
All the HTML elements that you want to visually align with GPU content on the canvas need the position:absolute CSS property.
You can set this with inline styling on the element or a CSS stylesheet.
The core HTML elements that cables.gl ships already have this style by default.
2) Set HTML elements as siblings of the <canvas> element and share the same anchor as the </canvas> element
Basic case:
When
your cables patch will fill the entire body of the page ('fullscreen') OR iframe, and there is no other HTML content on the page outside of cables.gl canvas
THEN as long as your cables generated HTML element have position:absolute property they will also use the HTML body as an anchor. And with the <canvas> as the only element on the page then all further generated elements will be siblings of the canvas. Because of the use of iframes this is the case in the previews of your patches you see in the cables.gl editor and patch page views.
Complex case:
If your cables patch is embedded in a page with other HTML content then you really need to understand the details of the other CSS position modes. You have to ensure that generated HTML elements are appended as siblings of the <canvas> in the DOM.
[See this separate tutorial I prepared for a deep dive on CSS positioning of elements in cables.gl.]
(#879)
3) use appropriate transform ops
The first step is to dynamically set the positions of elements in a cables patch use the op TransformElement.
If you use chrome inspector you will see this op dynamically sets the top and left css properties of the specific elements.
See the example patch for TransformElement here
To get HTML elements perfectly aligned with content drawn on the canvas see the op TransformElementWorldRect from AccessExt team.
This will also allow you to set size of elements dynamically taking into account camera perspective.
Check the documentation and example for TransformElementWorldRect here
What about non-rectangular visuals aligned with non-rectangular HTML elements?
I've only tested the approach above with rectangular HTML elements. If you go to this patch and click drag to rotate the camera you will see that this means the HTML interaction element may not perfectly conform to the visual shape.
(Note I left the button border to illustrate this but you can also hide that with CSS and make 'invisible' HTML buttons)
For many usecases this is not a problem but if you really need perfect hittest then here are two possible approaches:
3.1.7 Position outer panel in world space and then use regular HTML flow inside
You can take an approach when you position an outer panel HTML element in world space, aligned with content on your cavas.
Then you can make that panel a parent. Inside place child HTML using regular HTML positioning and CSS. This is a good way to make 2D control panels.
You can see an example of such a 2D control panel in cables.gl here.
In this case only your control panel outer parent element needs position:absolute and to be transformed using ops. The inner child button elements use normal flow (or flex flow etc) and the browser will decide their positions inside the parent.
3.2 Enabling keyboard navigation
A mouse or pointing device is impossible to use for many people.
Hence enabling keyboard navigation is a basic requirement for making accessible web experiences.
See this webaim article for more information
In terms of making keyboard accessible web apps we have some background info and then this part of the guide will cover a few main techinques
There is also dynamically setting focus, we will cover that in the next section.
We won't cover 'skip to content' links but these would be relevant in the context of an entire website.
3.2.1 Expected keyboard use
When you first look into this topic it seems to be all about the TAB key but there is more to it than that.
For the basics see the bottom half of this WebAim page
If you are designing a complex web app UI it's important to appreciate that Sighted and non-sighted keyboard users navigate differently.
This post has some great information.
A sighted user scans the web page visually and can scroll a standard page with
For interactive elements, a sighted keyboard user will see something they want to click on. Then they can:
A non-sighted user will have their screen reader read out the webpage. It reads in DOM order.
Bear in mind if there is interactive content like a link a non-sighted user will not see it in advance. They will only discover it when it's read out and their keyboard focus will already be there.
For interactive elements screen reader users expect to use same interaction keys as sighted keyboard users.
Both these groups have strong expectations around what should be interactive and where pressing TAB will be able to get them. For example they would expect most websites have a main menu and pressing TAB is eventually going to enable them to be navigating through that menu.
Note that TAB cycling will also take them off the website content and onto the browser controls. For example the back button, nav bar etc.
For this reason it's important not to trap them with any fancy javascript that would prevent cycling through TAB leaving the DOM.
✨ 3.2.2 How to test keyboard navigation in cables.gl
The first practical thing to understand is how to test keyboard interaction.
Currently the cables.gl editor redirects the TAB key so you can't test it in the editor.
Instead view your patch on its 'patch page' or the fullscreen /view/ version then you should be able to use keyboard shortcuts like TAB to navigate through interactive HTML elements.
Feature request for the cables.gl editor not to redirect TAB key
✨ 3.2.3 How to enable focus indicator in cables.gl
A focus indicator is a visible change in state that shows where the keyboard focus is at in a given moment. i.e. it shows you which button you selected that will then react if you press ENTER.
You can do various kinds of styling for focus change but it should at least
See this WCAG rule for more details
For a demonstration go to this page and press the TAB key to see the focus indicator move between interactive content.
Unfortunately as of Nov24 the default stylesheet on cables.gl suppresses a focus indicator by default. So you need to reactivate this in your patch.
You can set a focus indicator in a stylesheet via the CSS op.
Use the :focus-visible selector and define the style changes you want for the focus indicator inside.
If you use outline you must also add the !important property, because cables.gl default stylesheets otherwise prevent use of outline.
For example this will set a blue outline, with an optional little animation to make it even more obvious.
For a demo go to this patch page and press TAB, notice the blue outline
Remember you will need to ensure any styling you use is actually high contrast in your design.
Here is a feature request for Cables.gl to have a focus indicator in its default css
Note
There is another similar CSS selector :focus.
Generally:
For more details on this see the MDN entry.
✨ 3.2.4 How to set if a HTML element is keyboard focusable (tabbable) in cables.gl
First off what is a focusable/tabbable element? By default any HTML element that can be interacted with (links, buttons etc) should be discoverable when the user presses TAB.
This is indeed the default in browsers so if an element is semantically declared to be a link <a> or buttion </button> you don't have to do anything. These will auomatically be discovered by users pressing TAB.
If you are adding interactivity to regular HTML elements (or potentially removing it from elements that have it by default) you can override this behaviour.
In regular HTML there is an attribute called tabindex. This was originally used for overridding the tab order of a page to be different to the DOM but this use is no longer recommended. (The DOM should be the one and only order of HTML elements).
These days tabindex is used for simply saying 'can the user tab to this element'
In cables.gl there are two easy ways to set keyboard focus ability
For a demonstration of both check this example patch
We will take a closer look at this topic again when we get to dynamically setting focus.
✨ 3.2.5 How to set correct activation events on interactive elements in cables.gl
When an element has keyboard focus you can then press another keyboard button to activate it.
Which buttons?
Semantically correct links (in <a> tags) should automatically have the interaction for ENTER, so no need to do anything there.
So how to set up the event handlers for buttons?
In principle adding the 'click' event handler to buttons will automatically make them respond to appropriate activation keypresses (and also the activate gesture using voiceover on ios).
There are three ways to add keyboard event listeners on an interactive HTML element
Note as of Nov24 the default listeners to trigger a click on the DivElement and Element op are only for pointing device clicks. Not keyboard SPACE or ENTER and not IOS voiceover.
Here is a feature request to add this functionality to the cables.gl DivElement and Element ops
✨ 3.2.6 How to set DOM order of HTML content in cables.gl
DOM order will set the order that interactive controls are discovered by Keyboard users cycling through with TAB.
What the DOM is and how to set the order is covered in more detail in the previous sections 1.7 and 3.1.2
Basically use the ElementChilds op to set the DOM order of HTML elements.
See an example of using the ElementChilds op here.
3.3 Semantic HTML
Back at the start we talked about the importance of proper semantic HTML for screen reader users. It also helps SEO and makes your own DOM easier to understand.
For example buutton elements must have the HTML tag <button> or role "button" in order for screen reader users to understand that they are, indeed, buttons.
3.3.1 Using the right HTML element tag
There is a whole arcane rainbow of HTML tags. The tag is the word that opens and closes the HTML element, e.g.
First rule of semantic web is if the element you are creating has an appropriate HTML tag, prefer that rather than the techniques below. This will generally provide better default behaviour for all users.
3.3.2 WAI-ARIA Roles
Where the HTML element tag is either not specific enough or provides unwanted default behaviour you are free to use other tags, as long as you mark up the correct semantic meaning using WAI-ARIA Roles.
Roles are added to any element with an attribute.
For example to mark a div as a button:
Roles can easily be misused to create confusion, so be careful.
Check out this guide from MDN on the overall concept and list of roles
In cables.gl you can add roles by
[Here is an example patch in cables.gl where you can see icon buttons marked up with their correct role.] (https://cables.gl/edit/8Znrok)
3.3.3 Hiding elements from accessible technology
Some HTML elements should be hidden completely from accessible technology. This can be done with the aria-hidden component.
The two main usecases for this are:
Be careful
It's important not to overuse aria-hidden because generally we want sighted and non-sighted people to have the same experience as far as possible. That's also less web development work for you creating separate experiences.
Usage in code
Usage in cables
Link to an example patch for setting aria-hidden in cables.gl
Link to the MDN article on aria-hidden
See this article on making content invisible for all or just some users
3.3.4 aria-disabled
Aria-disabled attribute is inteded for buttons or interactive elements that should be indicated as disabled, the equivalent to being visually 'greyed out'.
Before you use this step back for a second and ask yourself do you even need the button to exist at all while it's disabled?
This will depend on expectations.
For example if the user should advance through several slides of text by pressing 'next' and 'back' buttons does the first slide need a 'back' button at all? You can't go back because it's the first slide but potentially the user does not need to even consider that going back is a possible functionality until they are on the second slide.
On the other hand for a more typical web form the user will probably expect there to be a 'submit' button. It may make sense to provide that button but leave it disabled. (But then you should also provide some info so the user can understand why it's disabled.)
In this case add the aria-disabled attribute and screen readers will report the element as 'disabled' or 'dim'.
Usage in code
Usage in cables.gl
Would it be enough to take the element out of the tab order with tabindex="-1" (AKA make it not keyboard focusable)? Doesn't that flag it's not interactive?
Don't rely on this to remove a button from discovery by screen readers. Users are not always navigating by searching for interactive elements in the tab order. A screen reader (particularly voiceover on ios) will still discover elements outside of the tab order. You must have aria-disabled on a button discovered this way or there is no way for the end-user to know that the button is disabled.
Do check the MDN article on aria-disabled for even more details
FYI I haven't focused on web forms much because it's not a common cables.gl usecase. If you are making forms there is lots of information out there about how to build nice accessible forms, with features like drawing attention to incomplete information that would lead to a disabled button. Here is a WAI tutorial on making accessible forms
3.3.5 Labels and Descriptions
Ok so we talked about tags and roles, we can understand that a <button> is a button. But how does a screen reader announce what a button actually does?
The standard case is that it takes the text from the element itself. e.g. This cancel button has inner text that describes it.
It's not always that simple, what if the button had an icon instead of text?
To cover these cases there are four further aria attributes that make content semantically accessible by giving elements a label and/or description.
There are two approaches with these elements, one where they specific the text for screen readers and another where they point to where in the DOM there is existing visible text that labels this element. Nominally t0he visible text approach is preferred so that sighted, non-sighted and partially-sighted users have the same experience as far as possible.
Why would the label for an element be visible somewhere else in the document? That might sound confusing but it's common with complicated web components. A component widget might be made of many HTML elements and we need to highlight which specific elements are the label or description of that widget. See the example for a dialog box below.
aria-label and aria-labelledby
What does it look like in code?
aria-label
aria-labelledby
Usage in cables.gl
Check the MDN documentation because there are certain roles that cannot have a label.
aria-description and aria-describedby
What does it look like in code?
aria-description
aria-describedby
Usage in cables.gl
3.3.6 Examples of semantics in use
Link role
Users have different expectations of links and buttons. Notably external links will take them somewhere else and may cancel current progress in a web app, or may open a new window which can be confusing.
For users of accessible technology in principle an element with a link tag <a> and a valid href attribute is already semantically identified as a link.
Any other element will need to have role="link" added.
Check out MDN as always for more info on the link role
Note on opening in a new window
With the link role I would ask you to consider also the context of your app, if your user is pressing buttons 90% of the time that open panels in your app would they be confused if a button suddenly navigated them to a completely new page? Even if it says it's a link, in some pages links are only for internal navigation. Hence you may want to add an 'opens in a new window' warning.
For sighted users this is something that can be achieved with tooltips, and for non-sighted users you could put an aria-description "opens in a new window".
This is theoretically a case where you would use aria-describedby to reference the actual tooltip element, but I had issues where adding the tooltip element created other bugs in screen readers.
So in the end I made the tooltip aria-hidden and put an aria-description with the tooltip text. This ensures sighted and non-sighted users get the same information.
See the tooltips section below in section 3.5
Semantic Dialogs
Imagine you have two or three dialogs or menu panels open with buttons. Users can cycle between the buttons with TAB.
How does a screen reader user know if they are on the close button of Dialog A or B? Ideally they would know the same way the sighted user knows which is because there is a heading at the top of the relevant dialog that says it's A or B.
For a dialog that is relatively simple, like a confirmation dialog, it also makes sense to attach the description text to the dialog parent.
For this we use the dialog role and aria-labelledby and aria-describedby attributes.
[Here's an example of two dialogs in cables.gl]
(https://cables.gl/p/sTRtGs)
More info on the dialog role from MDN
If you have a permanent group of controls consider using the toolbar role instead. (also with aria-label or aria-labelledby)
[More info on the toolbar role from MDN] (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/toolbar_role)
3.4 Visual accessibility
So far we focused on assistive technology like screen readers to create a foundation of accessibility in the structure of our web app. Now we bring visual accessibility back into the mix as well.
A good reference for ensuring visual accessibility is this checklist from the IBM accessibility toolkit
3.4.1 Not relying on only colour differentiation for meaningful information.
There are two major principles with accessible use of colour that get confused.
The first rule is that you should not rely on colour as the only means to convey some given piece of information. There should also be a change in form, text etc.
This rule is needed because some people can't see colour at all.
e.g. If you are using a figurative traffic light in your UI, don't just rely on the colour change from red to yellow to green. You could also use position (just like a real world traffic light) with green at the bottom of three lights and red at the top.
Or if you have a graph with colour also convey the results the graph shows in accessible text. You are already providing accessible text for screen readers so you could also supply this visually in a caption or tooltip.
More info about this rule in WCAG 2.2
3.4.2 Contrast
The second principle is to ensure appropriate contrast between colours. In particular between any text and the background.
This is because some people can't see colour at all or are partially colour blind. They can see the difference in two colours by their light-dark contrast. So we need to ensure that contrast is appropriate.
With contrast there are two levels known as AA (minimum) or AAA (enhanced). Aim for AAA if you can.
The requirements are
AA Contrast ratios between text and background
Regular text 4.5:1
Large text 3:1
AAA Contrast ratios between text and background
Regular text 7:1
Large text 4.5:1
How to ensure contrast?
*Here is adobes colour contrast checker tool. Note in the top right you can select AA or AAA level.
*A three way colour contrast checker, because it's often a three way problem. Black text on white is fine but then what should the link colour be? .
The WCAG 2.2 AA guidance on minimum contrast for text
The WCAG 2.2 AAA guidance on enhanced contrast for text
3.4.3 Visual distinction of interactive elements
All users need to be able to visually distinguish interactive elements in your UI. If people can't understand that a button is a button then they won't click it.
I won't go into general visual UI design principles here, only point out some specific accessibility recommendations:
Focus indicators are also an important part of visual accessibility, they are covered above at section 3.2.3.
3.5 Cognitive accessibility
Cognitive accessibility covers topics like memory and attention.
As with visual accessibility it has a lot of synergies with general UI design. Lowering the cognitive load to use your web app is beneficial for every user.
3.5.1 Tooltips showing alt-text for icons
Where Icons are used (e.g. on buttons) how does the user know what these mean? You could have a tutorial or separate reference document. But why not inform the user with a tooltip every time they hover the button? Then they can also teach themselves the meaning of icons without a tutorial or other reference.
Tooltips should appear on hover and also for keyboard users appear when an element receives keyboard focus. (along with the focus indicator we already discussed).
The text in the tooltip should generally be the same as the accessible name announced to screen reader users, i.e tooltip text should be the same as the aria-label attribute.
Some aria info on providing tooltips
✨ 3.5.2 How to create tooltips in cables.gl
Theoretically there are a number of ways to do it, and various guides on the web.
For a quick solution in cables.gl use the tooltip related ops in the AccessExt team.
See this patch for a demo of tooltips
In short
Note on AccessExt tooltip implementation
Note that after some experimentation I did do things differently with the tooltip implementation compared to the accessibility recommendations you will find out there about regular web pages.
In case the gritty details matter for your usecase:
3.5.3 Tutorial pointer
An interactive tutorial is a great way to introduce the general functionality of your app. It's quite a big undertaking and I won't try to write a complete guide to designing a tutorial here.
I will instead point out how to implement one specific feature of a tutorial, which is a big arrow or similar that literally points to other elements on your UI.
✨ How to create pointers for tutorials in Cables.gl
Here is an example patch showing how to implement a big red arrow pointing at existing UI elements in cables.gl
3.6 Dynamically moving focus
We have discussed the topic of 'focus' already, but let's get more specific.
A browser has a concept called 'keyboard focus' which is the currently selected interactive element. If you click a button that button also remains focused/selected after you click. If a keyboard user presses TAB they will cycle focus through the interactive elements. When you apply a :focus-visible style then the browser will highlight the selected interactive element visually as well.
Furthermore a screen reader has an expanded concept called 'focus' which is the definition above PLUS the screen reader can also focus on non-interactive content. The screen reader is focused on the content that should be currently read out. The user can move the focus with hotkeys or in 'read the whole page' mode the screen reader will automatically move focus.
From the developer point of view there is a javascript function called element.focus() which will dynamically jump the focus directly to an element.
The critical thing to understand is dynamically moving focus means a screen reader will stop what it is doing and cut to a new position in the DOM and read that out immediately. This is fine if the user expects it, e.g. because they pressed a button that they would expect takes their focus somewhere else. But it's very confusing to screen reader users if dynamically moving focus happens when they don't expect it, so this technique has to be used very carefully.
3.6.1 ✨ How to move focus dynamically in cables.gl
You can use the SetKeyboardFocus op from the AccessExt team. (Not to be confused with the SetKeyboardFocusable op).
There is a prerequisite for a HTML element being able to receive focus:
For more info see this MDN article on tabindex.
With that prerequisite out of the way use the op SetKeyboardFocus from the AccessExt team to trigger the .focus() command. Specify the ID of the HTML element you want to focus on.
Ensure as above that you only set focus when the user expects it.
This is something you definitely want to test with a screen reader to ensure it works as expected.
Note
In case you were wondering 'why would I want to programatically set focus to a non-interactive element?' it can be that the first element in a modal dialog is non-interactive text, but it is valid to move the focus there as you want the dialog to be read out by a screen reader as soon as the user presses the button.
See this ARIA example of a modal dialog for more information
At the same time this text block shouldn't be something that is selected when the user presses tab because it's not interactive. So if you set tabindex=-1 it will be able to be programatically focused but not appear in the tab order.
Differences with voicover on ios
I had a lot of trouble getting .focus() to work consistently on ios safari with voiceover. It seems Apple adds additional conditions to when .focus() will work and these seem quite tied to specific structuring of the DOM or javascript function.
This is one of those times that the most valuable company in the world could bother to release some documentation. I'd happily develop to Apples standard if I knew what it was. It's a real shame as voiceover on ios is one of the most popular screen readers.
3.6.2 Auto scrolling on focus
The SetKeyboardFocus op has a property for auto scrolling on focus. You generally want to disable this for cables.gl usecases as you don't want the browser itself to do any scrolling.
Conclusion
There is a lot more to making accessible web apps, with many more UI patterns like modal dialogs, progress bars etc that I have not explored in this post.
I hope this will still be useful as a starting point and we can share further learnings together.
Feel free to reach out to me if you would like to contribute to the AccessExt team.
Beta Was this translation helpful? Give feedback.
All reactions