5 tips for making your app accessible

Accessible product development is the responsibility of all designers and developers. Building an inclusive experience: Why accessibility matters, introduced the greater customer base, gaining empathy, starting the testing process, and how to learn more about accessibility. In this post, we’ll look at 5 tips developers can use to build accessible apps:

1. Form labels

Financial software customers must be confident their invoice, estimate, or payment was accurately created. They must know if a form input is for a due date or invoice date. Which inputs are required? Which have been marked as invalid?

As Intuit kicked off a project to fix form labels in QuickBooks Desktop in 2013, My Blind Spot created a video showing how accessibility can be the difference in someone keeping or losing their livelihood. It follows Dixie, a bookkeeper who lost her sight and subsequently her business. She was not able to use the product because the form inputs and controls were not adequately labeled.

Watch Dixie, a video that kicked off Intuit's project to make QuickBooks Desktop accessible

In case we aren’t being clear, all form inputs must be labeled.

While there are designs that require a hidden label, we must ensure that all visible labels are connected to their inputs. Connected labels provide distinct information and allow users to click on the label to set focus on an input. This is especially important for checkboxes and radio buttons.

There are two methods for using the <label> tag: Belt and Buckle. Both of these techniques are excellent.

Buckle method: Use the “for” attribute on the label and match its value to the input’s “id” attribute. This allows more flexibility with layout, as the label and input can be created in different functions.

<label for=”dogName”>Dog Name</label>
<input id=”dogName” type=”text”>

Belt method: While less common, this method is useful when you are not able to use an “id” attribute on form inputs. Wrap the input and descriptive text within the label tag.

Dog Name
<input type=”text”>

Use Accessible Rich Internet Applications (ARIA) for labels

ARIA is a set of HTML attributes that redefine the role, state, label, and value of an element. ARIA attributes have no visual or performance impact. They don’t transform the experience, they surface information to assistive technology to let the user know what they can expect.

The first rule of ARIA is to not use ARIA. Use the label as your first, second, and third option. ARIA is great when the label is not possible for your design or existing code.

There are several ARIA attributes that can improve your form inputs.

Labels via aria-label and aria-labelledby

You can provide a label for your input via aria-label. This is helpful when you design has no visual label. This example provides a label for the checkbox that visually sits at the top left of a grid that enables selecting all rows.

<input type=”checkbox” aria-label=”Select all rows for editing”>

ARIA attributes are always more powerful than HTML, which means you can relabel inputs with the aria-label. This is helpful when the visual label depends on abbreviations or visual references.

<label for=”qty”>Qty</label>
<input type=”number” id=”qty” aria-label=”Quantity”>

There’s a hierarchy to the accessibility tree, much like specificity in CSS. The most powerful aria attribute is aria-labelledby. This uses a reference elsewhere in the DOM to provide a label. This reference can also be used by multiple inputs, although this isn’t a best practice.

The following input would be announced as the “Favorite Puppy Names text input,” because the aria-labelledby value overwrites the aria-label and label values.

<h3 id=”favpuppies”>Favorite Puppy Names</h3>
<label for=”favname”>Puppy Names</label>
<textarea aria-labelledby=”favpuppies” id=”favname”></textarea>

Providing context: Sometimes, inputs are also provided with additional context. The following attributes provide additional information about states, label, and value.

Placeholder: The placeholder is meant to suggest an option, such as “Sarah Breedlove” for the Name input. This information is lost when the user adds a value. Placeholders are considered additional information and announced last by screen readers. This input would be announced as “Name text input {slight pause} Sarah Breedlove”

<label for=”name”>Name</label>
<input placeholder=”Sarah Breedlove” id=”name” type=”text”>

Title: The title appears when the user places their mouse over the input. It also provides additional information about the input. Unfortunately, the title attribute is only available to mouse and assistive technology users.

<label for=”name”>Name</label>
<input placeholder=”Sarah Breedlove” id=”name” type=”text” title=”This must match the name in your passport.”>

Descriptive text: The Intuit Design Systems suggests avoiding placeholder and title attributes. Place descriptive text below the input, which is available to all users. This will be connected to the input via the aria-describedby attribute. The descriptive text is considered additional information and will be announced last.

<label for=”name”>Name</label>
<input id=”name” type=”text” aria-describedby=”nameDesc”>
<span id=”nameDesc”>This must match the name in your passport.</span>

Required, Readonly, and Invalid: Make sure you also programmatically define the inputs states.

Required (aria-required): Don’t depend on “*” or visual design to specify which inputs are required. Use the required attribute, this can also inherit browser-based validation. The aria-required attribute does not trigger browser-based validation.

<label for=”name”>Name</label>
<input id=”name” type=”text” aria-describedby=”nameDesc” required>
<span id=”nameDesc”>This must match the name in your passport.</span>

Readonly: Some inputs are pre-filled and/or controlled by other inputs. These have a value but cannot be edited by the customer. The Readonly attribute removes the input from the keyboard flow and typically reduces the text contrast. It can still be accessed, but not edited, by screen readers.

<label for=”name”>Name</label>
<input id=”name” type=”text” aria-describedby=”nameDesc” readonly value=”Sarah Breedlove”>
<span id=”nameDesc”>This must match the name in your passport.</span>

ARIA Invalid: Let assistive technology users know which inputs have been marked as invalid with aria-invalid=”true.” This should be combined with aria-describedby and/or aria-errormessage

<label for=”name”>Name</label>
<input id=”name” type=”text” aria-describedby=”nameDesc” required aria-invalid=”true” aria-errormessage=”nameError”>
<span id=”nameDesc”>This must match the name in your passport.</span>
<span id=”nameError”>Name does not match legal name</span>

2. Use a button

All interactive elements must be keyboard accessible. There are two options: button and link. Buttons trigger actions, such as open menus, expand content, submit a form, or refresh values. Links take the user to new content, such as navigation and references to help documentation.

Avoid pseudo-buttons

Divs and spans are not buttons. They are containers that carry no semantic or interactive importance. You can make them work with a mouse by adding onClick, but that will only support a small portion of your customers. They do not receive keyboard focus and do not pass the onClick event handler when pressing the enter or spacebar keys. This is also true for all other non-interactive elements (p, li, td, i, small, section, h3 ….)

<div class=”btn edit” onClick=”foo();”>Edit</div>

To solve this you have to reset the role, make it focusable, and add event listeners for enter and spacebar

<div class=”btn edit” onClick=”foo();” onKeypress=”bar()” role=”button” tabindex=”0”>Edit</div>

Even this will not be fully supported by all assistive technology. Just use a button.

Watch the Just Use a Button tutorial

Button markup

Buttons are complex interactive elements that carry significant user experience options. Yet the markup is simple.


Extend Button role and state:

  • Disabled: Button doesn’t receive focus, cannot be clicked.
  • Type: submit, reset, button. Submit and Reset have clear purposes. Type=”button” has no default behavior.
  • Use aria-label for buttons that depend on background images or icon-fonts.
  • The aria-haspopup attribute lets users know this will trigger content to appear within new content sections. Most common values: true | dialog | menu | listbox.
  • The aria-pressed attribute lets the user know which button in a group has been selected.

3. Links must have an href

The “<a>” tag is what made the internet successful. It provides access to information from around the world. This basic tag has two distinct purposes, which has led to terrible misuse and inaccessible applications.

Anchors vs. Hyperlinks

An anchor is an <a> tag without an href attribute. It provides a target for deep linking, such as a Frequently Asked Questions page:

<a href=”#answer2”>How to paper train my rabbit?</a>

<h3><a name=”answer2”></a>Paper train your rabbit</h3>

Anchors do not receive keyboard focus and do not send onClick event from the enter or spacebar; as they are not interactive.

Anchors with an onClick event listener are neutered links, they will not work with a keyboard or assistive technology. Please use a button instead of a link to trigger an action.

<a onClick=”mouseOnly()”>Edit</a>

Make it a link: All links MUST have an href attribute. The href value should either be a URL or in-page reference.

<a href=”https://archive.org/”>Internet Archive</a>

Extending the link: Extend the role, state, and label of a link with the following attributes.

  • Hreflang: The targeted content is in a different language. 

<a href=”https://www.msf.org/arhreflang=”ar”>Doctors without borders (Arabic)</a>

  • Rel: The relationship of the link target to the web site.
  • Target: Originally used with opening content in specific frames, often used for opening a new tab/window, combine this with title=”Link will open content in a new window/tab.”
  • Use aria-label for links that depend on background images or icon-fonts. It’s also important for avoiding duplicate links.

<a href=”/futures” aria-label=”Learn more about Technology Futures”>Learn more</a>

  • The aria-haspopup attribute lets users know this will trigger content to appear within new content sections. Most common values: true | dialog | menu | listbox.
  • Add aria-controls when the link manipulates content on a page, such as letting the user know this link will control the information displayed in an expanded region.

Preventing the default behavior: Links and buttons have specific behaviors and do not require javaScript. However, you will need to use preventDefault prior to running your custom event. This has already been included in most platform event handlers.

4. Focus management

Modern web applications use single page architecture, and manipulate the display of content within the page or introduce panels, drawers, or modals. It’s critical that focused is moved to content when the page is changed dynamically.

This allows our keyboard users to immediately interact with the preferred content. For instance, adding a new customer may insert a panel with a new customer form. The user should immediately create the customer. They shouldn’t have to tab through the page before entering the panel. It should just work.

Assistive technology, such as screen readers, work off of a buffered view of the screen and do not reflect dynamic changes. Setting focus on new content triggers the screen reader to refresh their buffer and begin announcing new content.

Nicholas Zakas, JavaScript pioneer and creator of ESLint, wrote the essential article on managing focus: Making an accessible dialog box.

There are several interesting focus moments within the dialog life cycle.

  1. Button receives focus and user hits enter key.
  2. Button is marked as last focused item.
  3. Dialog box appears on page.
  4. Dialog, or child element, receives focus.
  5. Background content is blocked from receiving focus.
  6. Close button, submit button, or escape key closes dialog and removes it from the DOM.
  7. Background content is no longer blocked from receiving focus.
  8. Focus is set on the button that triggered the dialog to be displayed.

Much of this work is already included in the QuickBooks Online and React platforms.

5. Headings, landmarks, and page titles

HTML elements carry specific semantic and structural roles. Use the correct HTML elements to define the structure of your application and power multiple navigation methods for assistive technology users.


Next to accurate form labels and accessible buttons, inclusion of headings is the highest request from screen reader users. Headings (h1, h2, h3, h4) define the structure of your page and allow a user to quickly navigate from the top to subsections.

The top most heading is your h1. This may be the website logo. You should also use an h1 for the top heading of a panel or drawer, where the user does not have access to the background page.

The h2 describes the purpose of the screen, for instance “Invoices.” The h3 headings describe subsections of the screen, such as “Outstanding Invoices.” Headings define structure, not design. Don’t use a heading because the design has an area with large text.


Applications have distinct visual sections: header, navigation, footer, main, and search. These are represented with HTML sectioning elements: header, main, aside, footer, and nav. We can also extend these with ARIA.

Landmarks provide semantic structure of the page. Add the aria-label attribute to sections that may be repeated, it does not need to be highly descriptive.

<nav aria-label=”Main”>
<nav aria-label=”Secondary”>

Use ARIA role=”region” and aria-label to define important sections of the page, such as an invoice’s subtotal.

<div role=”region” aria-label=”Subtotal”>

Page Titles

The final piece of page structure is the title. You need to continually update the page title as the user moves through the application. Consider this workflow:

  1. Start on invoice page. Page title = Invoices | QuickBooks Online
  2. Create a new invoice. Page title = Create new invoice | Invoices | QuickBooks Online
  3. “Add a new product” panel appears within new invoice screen. Page title = Add new product | Create new invoice | Invoices | QuickBooks Online

The page title appears in the browser tabs, which is key for customers that open multiple tabs. Screen reader users request page title when confused by content.

Page title HTML

<title>Invoices | QuickBooks Online</title>

Update page title with React

componentWillMount() {
document.title = ‘Create new invoice | Invoices | QuickBooks Online’;

Accessibility requires less time than fixing code

The key to building accessible experiences is to use standard components. HTML components carry significant semantic and user experience patterns. You are responsible for restoring these when you reach for a “div”. QuickBooks Online and React provide standardized components that include accessibility hooks, focus management, and semantics.

Accessibility isn’t difficult or time-consuming. Yet, fixing buggy code is hard and expensive. Start on the purpose of your screen, not the design, to build delightful experiences.

Every small improvement is important. Start today by using your product without a mouse and running an audit within Chrome. Test early, test often, and most importantly, include a diverse group of customers in your testing.





Leave a Reply

Your email address will not be published. Required fields are marked *