Labels and hints

Labels and Hints in DLS v7

Cynthia Mo

Engineer I

  • August 11, 2023
  • 10 min read

The DLS (Design Language System) is in the midst of building our next major version, v7, of our React components, and we wanted to give a deeper look into how we’re tackling accessibility with regards to labels and hint text.

As a side note, DLS v7 is a work in progress, so what you see here might change.

Labeling and Accessibility

For many components, a label is required. This label should be programmatically associated with the component to ensure that screen reader users receive the same contextual information available to sighted users.

With this in mind, we wanted to offload the burden of handling accessible labels off the consumers as much as possible while still providing flexibility for custom labels.

Providing Labels

Native HTML and ARIA offer many avenues to provide labels. How to properly label depends on the element itself. Some elements have native HTML, so we strive to use semantic, native HTML where possible. My go-to resource is the Mozilla MDN docs to get element specific information.

Native HTML

Using native HTML is preferred because it’s more universally supported and understood by assistive technologies (e.g. screen readers). For example, for <button> elements, its inner text is its accessible name, which effectively functions as its accessible label.

<button type="button">Add to favorites</button>;

This button will read “Add to favorites” to screen reader users, which is our desired behavior.

Another common example is <input> elements. The native HTML approach is to use a <label> with a for attribute (htmlFor in React) that has the id of the input its labeling.

<label for="peas">Do you like peas?</label>

<input type="checkbox" name="peas" id="peas" />

Code snippet pulled from MDN input.

ARIA

When native HTML isn’t feasible, use ARIA, specifically the aria-labelledby or aria-label. While these are powerful, they should be used judiciously, since over reliance or incorrect use of ARIA can harm accessibility rather than enhance it.

We’ll do a high level overview, but please refer to the links above for a deeper explanation of each attribute.

aria-labelledby

aria-labelledby allows us to pass the id(s) of other elements on the page to define an accessible name. An important use case is that it can take multiple ids, so you can associate multiple labels to an element. This will become important later in this blog, since we use this in DLS v7.

Another important thing to note from the MDN docs, is that ”aria-labelledby takes precedence over all other methods of providing an accessible name, including aria-label, <label>, and the element’s inner text”.

For the snippet below, the screen reader will read “Do you like carrots?” because its aria-labelledby is overriding the label that’s provided with the for attribute.

<label id="carrot-label">Do you like carrots?</label>

<label for="vegetable-checkbox">Do you like peas?</label>

<input type="checkbox" name="vegetable" id="vegetable-checkbox" aria-labelledby="carrot-label" />

For the snippet below, the screen reader will read “Do you like carrots?” because the aria-label is being overridden.

<label id="carrot-label">Do you like carrots?</label>

<input type="checkbox" name="vegetable" id="vegetable-checkbox" aria-labelledby="carrot-label" aria-label="Do you like peas?"/>

aria-label

If there is nothing visible in the DOM to use with aria-labelledby, then aria-label can be used to define a string value, which labels the element. Since this content is not visible, be sure to use this cautiously because it introduces invisible content that can be missed by sighted users.

This pattern is commonly used to label icon buttons without visible text, such as a close button.

We don’t use this pattern as much within our DLS React components, but it’s still available for consumers to pass through via props. We prefer aria-labelledby over aria-label because it offers more flexibility, especially with multiple labels.

<button aria-label="Close">

// insert close icon

</button>

Labeling Considerations During the Dev Build

During the build, our engineers had a few considerations in mind:

  1. Provide an easy way for consumers to import components that match the designs in Figma
  2. Be flexible in our component API to allow customization
  3. Offload the responsibility of accessibility to the DLS (as much as possible)

Let’s dive into what these mean for our component code.

Matching our Figma Designs

In Figma and in our docs, you’ll see an input component used with a label. With v6, consumers had to import the <Input> and <Label> React components separately and configure the HTML attributes themselves in order to match the designs. This presents opportunity for confusion and accessibility errors.

Here’s a simplified v6 React code sample:

import { Label, Input } from '@americanexpress/dls-react';

export const Form = () => (

<>

<Label htmlFor="name-input">

Please enter your name

</Label>

<Input id="name-input" />

</>

);

Assuming that this is the common use case, we saw an opportunity to streamline this. If an <Input> should always be used with a label (most likely the DLS <Label>), then why don’t we ship the label within the component itself? Thus, the consumer doesn’t need to import the <Label> every time they want to use <Input>, and the DLS team can handle the HTML and ARIA attributes to guarantee it’ll be accessible.

Here’s a simplified v7 code sample:

// consumer application

import { Input } from '@americanexpress/dls-react-seven';

export const Form = () => (

<Input id="name-input" label="Please enter your name" />

);

For the consumers, if they want to use the built-in label (that matches the Figma designs), they just provide an id to the <Input> and a string to the label prop, and the component will handle the rest.

Within the React component:

// v7 Input.jsx

import { Label } from '../Label.jsx';

export const Input = ({ label, id }) => (

<>

{ label && <Label htmlFor={id}>{label}</Label> }

<input id={id} />

</>

);

The built-in <Label> will match our Figma designs. We set the htmlFor id value correctly to associate the <Label> with the <Input> to abstract this away from the consumers.

Allowing Customization

As a design system, we strive to provide guidelines while being flexible to the varying needs of our users. With that in mind, we’ve built in ways for users to customize their labels if the built-in label doesn’t fit their requirements.

The label prop can be omitted to exclude our DLS built in label. In this scenario, the responsibility of providing accessible labels falls onto the consumer. Consumers can accomplish this with the aria-labelledby and aria-label attributes. For <input>s, the for HTML attribute with a native <label> is preferred.

Let’s say a consumer wanted to have different label styling.

import { Label, Input } from '@americanexpress/dls-react-seven';

export const Form = () => (

<>

<Label className="pad-2 dls-deep-blue" htmlFor="name-input">

Please enter your name

</Label>

<Input id="name-input" />

</>

);

If you aren’t using the <label> element, have different label placement in your designs, or have multiple labels, aria-labelledby can be used.

Recall that aria-labelledby can take multiple ids to apply multiple labels to an element.

import { Input } from '@americanexpress/dls-react-seven';

export const Page = () => (

<>

<p id="first-label">

First Label

</p>

<p id="second-label">

Second Label

</p>

<Input aria-labelledby="first-label second-label" />

</>

);

This example below has hint text (within the <p> tag) that sits between the heading and the input that the heading is labeling. This doesn’t match our design, and there’s no way to insert the paragraph hint text via props or React children. Also, our built-in label isn’t using an <h3> under the hood. This is a prime example where customization is needed. Luckily our component can handle this use case with aria-labelledby.

import { Input } from '@americanexpress/dls-react-seven';

export const Page = () => (

<>

<h3 id="name-heading">Name</h3>

<p id="name-hint">Please capitalize your name</p>

<Input aria-labelledby="name-heading" aria-describedby="name-hint" />

</>

);

You may have noticed the aria-describedby in the above example, which provides a good segue into our built-in hint text.

Hint Text

Often times, components benefit from including hint text to provide additional information to the user. We often see this in form inputs. Since our Figma designs include a hint text with inputs, we followed the same thought process as labels and decided to ship our components with the hint text built in.

Now, we’ll shift our focus away from aria-labelledby and aria-label and focus on aria-describedby and how it’s used for hint text and descriptions. I’ll be using “hint text” and “description” interchangeably in this section.

What’s the difference between labels and hint text?

A common accessibility question is when to use aria-describedby and aria-labelledby. It’s a fair question; they are indeed very similar.

To answer this, it’s helpful to reframe it by asking yourself what is essential to describing the purpose of the component (labels) versus what is providing additional information that might be needed (descriptions). A good rule of thumb is that the label should be concise, while the description can be more verbose. The MDN aria-describedby page provides more detail.

aria-describedby

Unlike aria-labelledby and labels, the hint text associated via aria-describedby doesn’t need to be visible. Thus, it’s possible to have hint text that’s available only to screen reader users. This is okay because sometimes sighted users can gather contextual information from the visuals of the component.

For example, in our v6 <DatePicker>, the date is shown like 8/17/2023, thus sighted users can infer that the forward slash / is needed when manually entering the date. It would be redundant to provide the same information visually, but it would be immensely helpful to provide that information to non-sighted users with hidden hint text.

Like aria-labelledby, aria-describedby can also take on multiple ids to associate multiple descriptions to an element.

Hint Text in DLS v7

Our component API reflects the points discussed above, and we follow the same considerations from labeling in our dev build. The the same as labels, with the exception that consumers can visually hide hint text with our isHintVisible prop.

Matching our Figma Designs

We’ve streamlined our <Input> to ship the hint text within the component itself. For the consumers, if they want to use the built-in label (that matches the Figma designs), they just provide an id to the <Input> and a string to the hint prop, and the component will handle the rest.

An example of the default use case, which is a consumer who wants visible hint text.

import { Input } from '@americanexpress/dls-react-seven';

export const Page = () => (

<Input label="Name" hint="Please capitalize your name" />

);

Visually hidden hint text that is still read to the screen reader:

import { Input } from '@americanexpress/dls-react-seven';

export const Page = () => (

<Input

label="Name"

hint="Please capitalize your name"

isHintVisible={false}

/>

);

Allowing Customization

If a consumer doesn’t want visible or screen reader hint text, then they can omit the hint prop. Unlike labeling, hint text isn’t required, so there’s nothing else required of the consumer to be accessible. They can use aria-describedby if they have different styling or multiple hints.

Different styling:

// consumer application

import { Hint, Input } from '@americanexpress/dls-react-seven';

export const Form = () => (

<>

<Hint className="pad-2 dls-deep-blue" id="name-hint">

Please capitalize your name

</Hint>

<Input

id="name-input"

label="Please enter your name"

aria-describedby="name-hint"

/>

</>

);

Multiple Hints:

import { Hint, Input } from '@americanexpress/dls-react-seven';

export const Form = () => (

<>

<Hint id="first-hint">

First Hint

</Hint>

<Hint id="second-hint">

Second Hint

</Hint>

<Input

id="name-input"

label="Please enter your name"

aria-describedby="first-hint second-hint"

/>

</>

);

Feedback

We want to hear from you! Are you excited about accessibility or have questions about the impact on your team’s work? Let us know on slack in #dls-tech or #dls-design or by emailing dls@aexp.com.

References

Questions?

Connect with the DLS Team on Slack or by email.

Resources

Check out additional resources.