JavaScript for accessibility
Basics of JavaScript, what can go wrong and how to fix it
Victor Loux
![AbilityNet](images/abilitynet.jpg)
Victor Loux
It's an ordered sequence of operations for solving a given problem. A program breaks down a complex problem into a series of simple operations. Here's a program that makes a burrito:
Begin
Get out the rice cooker
Fill it with rice
Fill it with water
Cook the rice
Chop the vegetables
Stir-fry the vegetables
Taste-test the vegetables
If the veggies are good
Remove them from the stove
If the veggies aren't good
Add more pepper and spices
If the veggies aren't cooked enough
Keep stir-frying the veggies
Heat the tortilla
Add rice to tortilla
Add vegetables to tortilla
Roll tortilla
End
(This is not JavaScript!)
Notice the different types of actions here:
The most interesting part of JavaScript is that it allows us to manipulate HTML and CSS in real time, and react to events in the browser. Check out the example below:
<div class="red">This box is red</div>
click
event on the <button>
element, and run some code when it is clickedclass
attribute, and the text it contains.Of course, this is a simplistic example, but you can do very complex things with JavaScript, such as loading content in real-time from a server without reloading the entire page, manipulating strings of text, hiding and showing elements on the page (e.g. menus, accordions), create custom interactive widgets, react to user input (keyboard, mouse and touch)…
When used correctly, JavaScript can greatly enhance the user experience. But if some users are not taken into consideration, it can make their experience worse. In the example above, we don't have live regions, so screen reader users are not aware that the text in the box has changed when they press the button. Another common occurence is working only with a mouse or touch, but not keyboard.
Below this paragraph is a console, which allows you to type some JavaScript code and immediately get the result. Try some simple instructions like 5 + 3
, or 40 * 28000
. To deal with text, you have to wrap it with quotations marks, "like this"
. You can concatenate (add) two strings of text with the +
operator: try it!
We will talk more about this later, but you will need to remember that in JavaScript, you need to add a semicolon ;
after each instruction.
In programming we use a lot of variables. These are “containers” that retain a certain value, and allows us to avoid repetition, or take data set by the user. Variables, as their name indicate, can change value over time.
Initialise a variable for the first time with the var
keyword, followed by its name, then its value. For example var age = 4
.
You can only initialise a variable once. To change its value after that, use an equal sign =
operator, without the var
keyword. Once you have variables you can then use them in place of a number, for example age + 5
.
Variables can take many possible type of values, for instance:
42
or 5.2
"apples"
true
or false
. This is used very often for making logical statements and conditions; and remembering the state of something (for example "pressed or not").person.name
.Try declaring multiple variable and adding them together. See what happens if you add a number to a string, or two strings together!
You may notice there are some rules about how you can name a variable. Variable names may only contain letters, numbers (but not in the first position) and an underscore (_). See what happens if you try to declare variables called my first name
, name-of-the-user
, or 2nd_favourite_colour
.
It's good practice to give descriptive names, rather than single-letter variables like in maths (x
, y
, a
…). A common thing to do is using a mix of uppercase and lowercase, for example var myFavouriteFruit = "apple"
.
An important part of programming is having a control flow. This is where our program can make decisions. The most common way to do this is with an if-else statement.
If condition is true, then
do something
else
do something else
The condition is always a boolean expression, i.e. it is either true
or false
. For example, 6 + 6 == 3
is false, but 6 + 6 == 12
is true. Of course there is little point in testing fixed values because they will always be true or false, but it becomes interesting with variables as we can change the behaviour dynamically.
Let's have an example. We will set a variable using the box below, to see if someone is over 18:
if(age >= 18) {
message = "You are an adult!";
} else {
message = "Hang on there kiddo, you can't buy alcohol";
}
message
is now "".
if (condition)
means "if the condition (what is between the parentheses) is true, then execute the following block".age >= 18
is the condition: it checks whether the variable age
is greater than or equal to 18 (>=
is like ≥ in maths).{ ... }
define a block. They delimitate the start and end of what we want to run. They're used everywhere in JavaScript, not just for conditions. It's also important to know you can nest them!else
statement; you can have a condition without it.In addition to the greater / lesser than operator (>, ≥, <, ≤) you will also encounter:
if(age == 36) { … }
This means "if the variable age
is exactly equal to" 36. Note that to say "equal", we use two equal signs; this is because a single equal sign is used for assigning a value to a variable. If we wrote age = 36
it would mean "set the variable age
to be 36", instead of comparing two values.
if(favouriteFruit != "banana") { … }
This operator, !=
, means not equal to (≠). You can use it to say what you don't want: it inverses the result.
Here are are nesting several if
statements. This allows us to have several conditions one after the other. If the user is under 18, then we check if they are 16 or over. If they are between 16 and 18, then we can check on another boolean variable (are they having a meal?): and if so we can give them a specific message. But this is a pretty long way to do it: we can also write it as...
if(age < 18) {
if(age >= 16) {
if(isHavingAMeal == true) {
message = "OK, maybe you can if your parents are here...";
}
}
}
You can test over more than one condition when using else if(condition)
. Conditions are tested in order until one is true; if none of them is true, then it will fall back on the else
block.
if (weather == "sunny") {
tip = "T-shirt time!";
} else if (weather == "windy") {
tip = "Windbreaker life.";
} else if (weather == "rainy") {
tip = "Bring that umbrella!";
} else if (weather == "snowy") {
tip = "Just stay inside!";
} else {
tip = "Not a valid weather type";
}
tip
is now "".
if(age < 18 && age >= 16 && isHavingAMeal == true) { … }
The &&
operator means AND. We can have expressions on both side of it, and the entire condition will only be true
There is also an operator for OR, which is two pipe signs (||
):
if(favouriteFruit == "orange" || favouriteFruit == "lemon") {
message = "Yum! Citrus!";
}
The block above will run if favouriteFruit
is either "orange" or "lemon", but not if it is anything else (like "apple" or "banana").
Think you got it? Have a play below!
You can try to:
7 < 10
, a < 16 && a > 18
…Functions are another important aspect of code. They allow you to repeat the same block of code without having to copy and paste the same code over and over. Functions are first defined somewhere, this is when we define what they will do
function sayHello() {
console.log("Hello, AbilityNet!")
}
We have the keyword function to say what it is (a bit like var
), and then the name of the function (sayHello), parentheses (we'll talk about these later) and braces to start a block, to define the start and end of the function code.
Once you've defined a function once, you can call it later in your program. This is done by using the name of the function with parentheses:
sayHello();
sayHello();
Our console will output:
Hello, AbilityNet!
Hello, AbilityNet!
Functions are interesting because they can also take arguments. This means what they do can depend on an input parameter. This is what the parentheses are for: they can contain a list of arguments. Our function sayHello
did not have any argument, but we can define them in the definition and the call:
function double(number) {
console.log(number * 2);
}
double(6); // 12
double(10); // 20
double(0.5); // 0.25
(you may note that in the definition, we actually called a function called console.log
, with a string as an argument. We'll talk about dots in function names later).
More arguments can be separated with a comma, like add(number1, number2, number3)
. Try to define and call such function that adds 3 numbers below:
We will now start to look at how JavaScript interacts with HTML and CSS. To modify elements on a page, we can first select an HTML element, in the same way that we do with CSS. We can then assign this HTML element to a variable, and then do things to that variable, like changing the text inside it, its attributes or CSS properties.
There are many ways to select HTML elements in JavaScript, but the easiest is document.querySelector(selector)
, where selector
is a string that contains a CSS selector. For example, "#weekends"
for the element with an ID of "hidden", ".menuBar"
for elements with a class of "menuBar", or "h2"
for all h2 elements.
The one downside of document.querySelector
is that it will only select the first element that matches the given selector. If there are multiple elements on the page, we would need to use something querySelectorAll
, but this requires using loops which we have not seen yet. Anyway, let's try it! Without touching the HTML pane, only the Javascipt code, try to get the second then the third box selected.
When we store an HTML element in a variable, its type is not a string or integer or boolean. It is instead what we call an object.
Objects are a relatively advanced programming concept, but to make it simple, variables that are objects can have their own “sub-variables”, which are actually called properties, and their own functions, which are called methods. If we had an object called person
, we might have:
person.name
, a propertyperson.age
, another propertyperson.sendMessage("Hey!")
, a methodOn the previous pages, we've seen an example of how properties and methods are used: with the dot notation. When we used console.log()
, we actually used the method (function) called log
, from the special object called console
. The console
object indeed has other methods we could use: warn()
, error()
…
Let's take the following HTML example:
<h2>Opening hours</h2>
<h3>Weekdays</h3>
…
<h3>Weekends</h3>
Now let's say we have selected the Weekdays heading element, and stored it into a variable called weekdaysHeading
. This object has properties that will change only what is in this heading, and not others. For example, we can do:
weekdaysHeading.style.color = "blue";
And the result will be:
Objects allow us to change things independently of other elements.
There are a wide, but limited, amount of properties and methods you can use on objects that represent HTML elements. Most of the time, they map to the HTML attributes of the element. For example:
element.style
, as we've seen above, is an object of its own — and each of its properties is the equivalent of a CSS property that can be set dynamically. For example element.style.color
or element.style.background
.font-size
and margin-top
become fontSize
and marginTop
. The same is true for some attributes that we can set directly on the element: we can set el.tabIndex
to modify the tabindex
attribute.element.setAttribute(attributeName, value)
. For example, if we have a heading <h2>Contact</h2>
and we call myHeading.setAttribute("id", "call-us")
, then the HTML will become <h2 id="call-us">Contact</h2>
.element.getAttribute(attributeName)
.Try to select different elements and set attributes and styles dynamically. For reference, you may want to have a look at the CSS properties reference (opens in new window) for a list of things to test.
Now that we've looked at functions and selecting HTML elements, we can start looking at event handlers. These are functions called when a specific event happens on an element that we've selected; an event can be a click (with a mouse, or touch on mobile), a key press, a value changing (e.g. a checkbox becoming unchecked), the mouse simply going over an element (without clicking), etc.
Consider the following HTML:
<button class="magic_button">Make box bigger</button>
<div class="box"></div>
Of course, nothing happens yet when you press it. We cannot just set the size of the box in code anywhere, because it would expand immediately and not react to the button. We want to create a function that will make the box bigger, say makeBoxBigger()
, and only call this function when the button is pressed.
This is done by using element.addEventListener(eventType, functionToCall)
. This is a method, applied to an element, as we've seen before. It says "keeps listening for eventType on this element. When this type of event happens, then call functionToCall".
So in our example, we'll have our element called magicButton
, the eventType
is going to be click
, and the function is going to be makeBoxBigger
. Let's try below:
In the example above, even though we used the click
event, our button still worked when we pressed the Enter or Space key. This is thanks to a convenience of browsers which fire a click event on keyboard and touch input, but this only works on inputs and buttons. If we were to create a custom component, we will have to implement both events independently.
Other event types exist for keyboard and touch, and more specific events can be useful for special interaction. For example we can use keyup
for when a key is released, keydown
for when it is pressed, touchstart
for when a finger starts pressing the screen… this allows us to create interactions like drag and drop, or games. For most keyboard-specific handlers, we are also able to get the specific key that was just pressed.
Exercise time! Recreate the demo above, but this time we want different interactions on click and on keyboard. Create a function called makeBoxSmaller()
by copying the function above and changing the + to a -, and copy the event listener to use a keydown
event instead of mousedown
.
One important method that we did not talk about yet is the element.focus()
method. Calling .focus()
, with no arguments, moves the focus to a specific element, just as if the user tabbed onto it.
Being able to set focus programmatically can have bad consequences for accessibility: it breaks predictability for keyboard and screen reader users, particularly when it is not expected. It can be a breach of SC 3.2.1 On Focus (opens in new window) or 3.2.2 On Input (opens in new window) because it changes context when the user does not expected.
However, when used wisely it can be useful! A common example is a modal dialog. Let's see that on the next page…
Let's talk a bit about ARIA. ARIA stands for Accessible Rich Internet Applications and is an extension of HTML that allows us to make complex web applications and widgets more accessible to people who use Assistive Technology. With ARIA, we can define custom roles, states, names with the value of an element, which allows us to fulfill the success criteria 4.1.2 Name, Role, Value (opens in new window) of the WCAG.
ARIA mostly adds new HTML attributes that start with aria-
, as well as the special role
attribute. A lot of the possible attributes are in fact already implicit in a lot of existing HTML elements. For example, <h3>
is equivalent to writing <div role="heading" aria-level="3">
, and the <nav>
element will be treated the same as <div role="navigation">
.
It would be redundant to write ARIA attributes on elements that already have semantics: and as Heydon Pickering says, ARIA isn't for making HTML accessible, it's for making inaccessible HTML accessible
.
Sometimes, native HTML elements won't cut it, because of complex interactions or implementations. Examples that you will have encountered include accordions: the expanded or collapsed state needs to be communicated non-visually, and this can be achieved using aria-expanded
. Text that changes elsewhere on the page needs to be announced, with aria-live
. Tabs can be made accessible as such by using role="tab"
and aria-selected
, despite not having any native element for them in HTML.
Here's an example of an accordion button. Sometime ARIA elements need to remain the same and are set in HTML only, but sometimes they need to be changed in JS for it to make sense:
Have a look at the code and now try to make the toggle button below accessible. At the moment, screen reader users do not know whether it is pressed or not; there is only a visual cue. Hint: use aria-pressed
.
Let's try to put it all together!
The following is an inaccessible modal dialog: the focus is not moved, the background receives focus with a screen reader and a keyboard. There is also no label on the close button.
The following is a correctly implemented, accessible dialog:
The developer tools / web inspector (F12) have a console that will run in the context of the page.