Tutoriales Writing Accessible Canvas Descriptions

Writing Accessible Canvas Descriptions

By Kathryn Lichlyter, Layla Quiñones

Introduction

In this tutorial, you’ll learn how to use describe(), describeElement(), textOutput(), and gridOutput(). These functions add labels to your canvas so that it’s readable for screen readers.

What is Labeling?

When you use the createCanvas() function, you create a canvas HTML element. This canvas element displays the image generated by your code as a bitmap (a raster graphic made up of pixels). By default, the canvas doesn’t provide any description of its contents to screen readers. That’s why we created the describe(), describeElement(), textOutput(), and gridOutput() functions. These functions add labels to your canvas that tell the screen reader how to describe it.

Why Labeling Matters

Screen readers are helpful for lots of people. If a site’s code isn’t properly labeled for assistive technologies, the screen reader software won’t be able to communicate what’s on the site. Making sure your code is readable by assistive software allows more people to engage with your work in meaningful ways.

Available Labeling Functions

p5.js offers four different functions for labeling your canvas:

  • describe() provides a description of the canvas contents. This function’s parameters include: text as the label itself; and display as an optional parameter to set the visibility of the label.
  • describeElement() describes a specific element or a specific grouping of elements in a canvas. This function’s parameters include: name as the title for the label; text as the label itself; and display as an optional parameter to set the visibility of the label.
  • textOutput() generates a list describing the canvas size, color, as well as each visual element’s color, position, and the area it covers within the canvas. This function’s only parameter is display as an optional parameter to set the visibility of the label.
  • gridOutput(), like textOutput(), generates a list of the canvas’s qualities and elements. Along with this list, this function also creates an HTML table that plots the spatial location of each shape within the canvas. This function’s only parameter is display as an optional parameter to set the visibility of the label.

Prerequisites

Your project’s code should be near completion before you begin labeling, but you should keep labeling in mind from the very beginning. To write clear and effective labels, you should have a clear understanding of the visuals your code creates within the canvas element. 

For example, if you started writing your labels before you had a clear understanding of the resulting visual of your canvas, your labels and your visuals may communicate different messages, like the code example below:

function setup() {
  createCanvas(100, 100);
  describe('A blue square in the center of a black canvas.');
}
function draw() {
  background(255, 192, 203);
  noStroke();
  fill(200,0,0);
 
  ellipse(67, 67, 20, 20);
  ellipse(83, 67, 20, 20);
}

Step 1 — Plan Your Labeling Strategy

Your labeling strategy will change based on the complexity and purpose of your project.

If you do not provide any labels in your code, screen readers will describe your canvas as a blank HTML element. No matter how complicated your project may be, always provide a brief description of your canvas in setup() using the describe() function. 

Within the describe() function, provide a 1-3 sentence description of your canvas in its text parameter. This description should only provide details about the visual elements of your canvas. 

function setup() {
  createCanvas(100, 100);
  describe('A red heart in the bottom right corner of a pink background.');
}
function draw() {
  background(255, 192, 203);
  noStroke();
  fill(200,0,0);
 
  ellipse(67, 67, 20, 20);
  ellipse(83, 67, 20, 20);
  triangle(91, 73, 75, 95, 59, 73);
}

You do not need to begin your description with “A p5 canvas element…” or anything similar, since the screen reader will declare the element type before reading your label.

Use the describe() label to describe your canvas as a whole. Use the describeElement(), textOutput(), or gridOutput() function to add more detailed labels to specific elements within your code.

The describe() and describeElement() functions allow you to freely author the text description of your canvas visuals. The textOutput() and gridOutput() functions will automatically describe the shapes on your canvas as a list of the canvas size, color, as well as each visual element’s color, positions, and the area it covers within the canvas. Unlike describe() and describeElement(), the textOutput() and gridOutput() functions can’t interpret your intention in using the shapes. Keep context in mind when choosing which function(s) to use. Is it better to describe a flower as “eight circles and a rectangle” using textOutput() and gridOutput() or as “a flower with red petals and a green stem” using describe() or describeElement()? What kind of labeling will provide the best description of your canvas? If you are creating larger visuals with many shapes, you might want to use describeElement() to label what each group of shapes is.

Do not use both the textOutput() and gridOutput() functions to describe the same canvas. Using both will cause similar descriptions to appear twice, which is hard to interpret using screen readers. The same goes for using textOutput() or gridOutput() with describeElement() labels. It’s best to choose just one of these functions to supplement your describe() label.

Complex Projects

Use vanilla ARIA labeling and custom-built fallback labels instead of p5’s labeling functions if your canvas:

  • Has content that changes extensively via external interactive elements (elements outside the canvas)
  • Interacts with DOM elements written outside of the canvas code
  • Requires the user’s attention if the canvas’s visual content changes
  • Has complex element layouts that cannot be accurately labeled with the describe(), describeElement(), textOutput(), or gridOutput() functions

For more information about fallback content, visit W3C’s Wiki. For more information about complex ARIA labeling, see Mozilla’s “ARIA states and properties”,  W3C’s “Using ARIA”, and MDN’s ARIA.

Step 2 — Write Your Main and Supporting Label(s)

Begin labeling your canvas using the function(s) that best serve your users.

While labeling, only provide descriptions of the visual aspects of your canvas. You don’t need to describe how or what functions create the visuals present on the canvas, only how the end result visuals appear within the canvas.

Using describeElement()

Sometimes the describe() label isn’t enough to recount the visual elements of your canvas. In cases where you can’t describe all the canvas’ visual elements in proper detail using describe(), use describeElement()  labels to provide individual descriptions for visual elements. 

The describeElement() descriptions should not contradict information provided in the canvas’ describe() label. Your describeElement() labels should have a unique title and a description about 1-2 sentences long. Be sure to only label the parts of your code that depict the most important visual aspects of your canvas.

Within each describeElement() label, discuss the important qualities of that element. Is the element animated? Is the element interactive? What meaning does the element provide to the project?

If your canvas contains any text() elements that are important to the general understanding of the image, make a separate label for them. Label any legible text with quotation marks around it, as in describeElement('Text', 'The words “hello, world” displayed in green at the center of a black canvas.').

As stated previously, you don’t need to start each label with “A p5 canvas…” or something similar. The screen reader will call out the element type before reading your labels:

function setup() {
  createCanvas(100, 100);
  //Provides an overall description of the canvas.
  describe('Text in the top right corner with red heart in the bottom right corner on a pink background.');
}
function draw() {
  background(255, 192, 203);
  noStroke();
  fill(200, 0, 0);
 
  textSize(12);
  textStyle(NORMAL);
  // Specific label for text.
  describeElement('Text', 'The text "Hello world!" in the upper left corner of a pink canvas.');
  text('Hello world!', 5, 30);
  // Specific label for the heart.
  describeElement('Heart', 'A red heart in the bottom right corner.');
  ellipse(67, 67, 20, 20);
  ellipse(83, 67, 20, 20);
  triangle(91, 73, 75, 95, 59, 73);
}

Limit the number of describeElement() functions present within your code as much as possible. If you have to use more than 10 describeElement() functions to describe your canvas, consider using a labeling strategy that affords more complexity (such as vanilla ARIA labeling).

Using textOutput() or gridOutput() 

textOutput() and gridOutput() automatically calculate label information based on the code of the visual element, such as its size, color, and shape. Unlike with describeElement(), you only need to use one label to describe all your canvas’s visual elements and the only parameter you set is display, which will activate the label. 

The information provided by textOutput() and gridOutput() will automatically generate in a hidden div screen readers can access. If you would like to view how these functions are interpreting your code, use the LABEL argument to translate the output in the space underneath the canvas.

Projects with Animated or Interactive Elements

Individual interactive elements, such as HTML buttons, dropdowns, or inputs, don’t need labels. These elements are built outside of the p5.js canvas and are interpreted by screen readers. This means the textOutput() and gridOutput() functions won’t provide any information about these interactive inputs.

If a canvas element is animated and/or interactive, represent its current state or qualities in the label. So long as you place the functions within the draw() function, they will automatically update with the shape’s new information. If you are using describeElement(), use template strings to update the element’s description:

let x = 0;
function setup() {
  createCanvas(100, 100);
}
function draw() {
  if (x > 100) {
    x = 0;
  }
  background(220);
  fill(0, 255, 0);
  ellipse(x, 50, 40, 40);
  x = x + 0.1;
  // Label updates with shape's translation.
  describe(`A green circle at x pos ${round(x)} moving to the right`, LABEL);
}

Labeling Do’s and Don’ts

Don’t use screen reader labels as a way of commenting your code. Labels should only summarize the resulting visual elements within a canvas. 

function setup() {
  createCanvas(100, 100);
  //Provides an overall description of the canvas.
  describe('This heart was made by overlapping two circles and a triangle.');
}
function draw() {
  background(255, 192, 203);
  noStroke();
  fill(200, 0, 0);
  ellipse(67, 67, 20, 20);
  ellipse(83, 67, 20, 20);
  triangle(91, 73, 75, 95, 59, 73);
}

Don’t overuse screen reader labels, as you may end up complicating the screen reader’s interpretation of the canvas rather than helping it.

function setup() {
  createCanvas(100, 100);
}
function draw() {

  //Don’t use describeElement() to label every function used to build a visual element.
  background(255, 192, 203);
  describeElement('Heart', 'No stroke applied to the heart.');
  noStroke();
  describeElement('Heart', 'The heart is colored red.');
  fill(200, 0, 0);
  describeElement('Heart', 'The top of the heart is two circles.');
  ellipse(67, 67, 20, 20);
  ellipse(83, 67, 20, 20);
  describeElement('Heart', 'The bottom of the hearts is a triangle.');
  triangle(91, 73, 75, 95, 59, 73);
}

Do make your label descriptions short and accurate. The recommended length for label descriptions is one to two sentences. Use full sentences for your labels, and write in the present tense when describing elements.

Step 3 — Test Your Labels

Be sure to test your labels before publishing your sketch. By default, there is no display of your labels and they will only be viewable by a screen reader. To see the label output during development, pass LABEL as the last argument in your labeling functions.

function setup() {
  createCanvas(100, 100);
}
function draw() {
  // LABEL shows the label next to the canvas.
  gridOutput(LABEL);
 
  background(255, 192, 203);
  noStroke();
  fill(200, 0, 0);
 
  ellipse(67, 67, 20, 20);
  ellipse(83, 67, 20, 20);
  triangle(91, 73, 75, 95, 59, 73);
}

When testing your labels, consider the following questions: 

  • Do your canvas labels provide enough information for someone to understand what the sketch looks like and its purpose? 
  • If this canvas exists on a web page among other content, will a visitor to the page have a good understanding of how the canvas relates to its surrounding context?

Be sure to remove the LABEL argument once you’ve tested the output. With LABEL active, screen readers are forced to read the fallback text and the visible label text when focused on the canvas. This causes unnecessary repetition of information.

You can activate or download a screen reader and use it to test your code. MacOS has a built-in screen reader called VoiceOver and there are free downloadable options for PCs. For more information about using screen readers, see W3 School’s “Accessibility Screen Readers”.

Conclusion

Once you’ve tested your labels, your canvas should be accessible to screen reader technology! This is an act of community care which allows more people to meaningfully engage with your project. You are modeling practices which contribute to a more inclusive and barrier-free internet.

To learn more about p5.js and accessibility, read How to Use the p5.js with a Screen Reader.