Accessible input tooltips with no javascript

Login form

The HTML

(ARIA and ARIA-associated attributes displayed in green)

<form action="">
  <fieldset>
    <legend>Login form</legend>
    <div>
      <label for="username">Your username</label>
	  <input type="text" id="username" aria-describedby="username-tip" required />
	  <div role="tooltip" id="username-tip">Your username is your email address</div>
	</div>
	<div>
	  <label for="password">Your password</label>
	  <input type="text" id="password" aria-describedby="password-tip" required />
	  <div role="tooltip" id="password-tip">Was emailed to you when you signed up</div>
	</div>
  </fieldset>
</form>

A few notes

No javascript is required because no states (attributes) need switching — the aria-describedby and id relationships are just there. We still need to reveal the tooltip on focus but this can be achieved with a simple CSS adjacent sibling combinator:

input:focus + [role="tooltip"] {
	display: block;
	position: absolute;
	top: 100%;
}

Button controlled input with live feedback

Because number input support is sort of patchy.

Add or subtract ten

The HTML

(ARIA and ARIA-associated attributes displayed in green)

<form action="">
  <fieldset>
    <legend>Add or subtract ten</legend>
	<div>
	  <label for="number">Current value</label>
	  <input type="text" role="alert" aria-live="assertive" readonly value="0" id="number" />
	  <button type="button" title="add 10" aria-controls="number">Add</button>
	  <button type="button" title="subtract 10" aria-controls="number">Subtract</button>
	</div>
  </fieldset>
</form>

A few notes

The aria-live="assertive" attribute means that the value of the text field will be spoken whenever it changes. This happens whenever you use the buttons to add or subtract 10 from the value.

The aria-controls attribute just confirms that each button controls the input. Different screen readers identify this accessible relationship in different ways, but it helps to know what you are affecting. JAWS sometimes announces a shortcut to the element that is being controlled. This is handy if it's a long way away in the document.

NVDA reads the button title attribute after the button text node. For example, "add button — add 10".

Progressive collapsibles

Building accessible, collapsible content sections from basic flow content.

First title in block of content

Aliquam tincidunt velit sit amet ante hendrerit tempus. Potenti et eros sed justo – commodo bibendum non at nunc. Fusce ac sodales magna. Suspendisse potenti cras molestie, risus a enim convallis vitae luctus libero lacinia. Cras molestie risus a enim ‘convallis vitae’ luctus libero lacinia.

Nulla auctor eleifend turpis consequat pharetra: Vestibulum sit amet ipsum lacus… Nulla vel magna sit — amet dui lobortis commodo — vitae vel nulla.

Curabitur consectetur faucibus nisl ac varius. Curabitur consectetur; faucibus nisl ac varius. Nunc iaculis risus vel orci ornare dignissim sed vitae nulla. Nulla auctor eleifend turpis consequat pharetra. Suspendisse potenti H20. Suspendisse potenti.

Another Third Level Heading

Aliquam nisl enim… tristique tempus placerat at, posuere in lectus. Potenti et eros sed justo commodo bibendum non at nunc. Aliquam nisl enim, tristique tempus placerat at, posuere in lectus. Sed dapibus, lectus sit amet adipiscing egestas, mauris est viverra nibh, iaculis pretium sem orci aliquet mauris. Duis sagittis, est sit amet gravida tristique, purus lectus venenatis urna, id ‘molestie’ magna risus ut nunc. Donec tempus tempus tellus, ac lacinia turpis mattis ac. Curabitur consectetur; faucibus nisl ac varius.

Nunc iaculis risus vel ‘Orci Ornare’ dignissim sed vitae nulla. Sed dapibus, lectus sit amet adipiscing egestas, mauris est viverra nibh, iaculis pretium sem orci aliquet mauris. Fusce ac sodales magna. Nulla auctor eleifend 23rd of May turpis consequat pharetra. Suspendisse potenti H20.

The HTML

(We start with just <h3> headings and other flow content. The other flow content is wrapped and the necessary button control and attributes are added with javascript. A click function just toggles the aria-expanded and aria-hidden states.)

<h3>
   <button aria-expanded="false" aria-controls="collapsible-0">What is this all about?</button>
</h3>
<div id="collapsible-0" aria-hidden="true">
   <p>Lorem ipsum with a <a href="http://example.com">link thrown in</a> etc.</p>
   <p>etc.</p>
</div>
<!-- next <h2> somewhere here -->

A few notes

Using both JAWS and NVDA with Firefox, focusing on the heading button indicates (a) the heading level, (b) that the control is a button and (c) whether it is in an expanded or collapsed state. In NVDA, for a collapsed region, "[The title], heading level 3, button collapsed" is announced. In JAWS, it is the same info but in a different order.

In JAWS 15, the aria-controls relationship is recognised and you are given the option to "use the JAWS key + ALT + M to move to the controlled element". This is an interesting addition and would be handy if the element was not the next node on the page (ie. out of TAB order). In any case, our script focuses the newly expanded "panel" which causes both readers to automatically begin reading the revealed content.

In this demo we use the `<h3>` element as the divider. You can tweak the script to use whatever heading level works best for your content.

Simple ARIA tab interface

A tab interface built on POSH and enhanced with ARIA via javascript. Just requires you to place the structure within a wrapping <div>. In our case this is identified by the class .tab-interface. No other classes are required.

Section 1

Duis sagittis, est sit amet gravida tristique, purus lectus venenatis urna, id molestie magna risus ut nunc. Aliquam nisl enim, tristique tempus placerat at, posuere in lectus. Vestibulum sit amet ipsum lacus. Suspendisse potenti.

Nulla lobortis tempus commodo. Fusce ac sodales magna. Cras molestie risus a enim convallis vitae luctus libero lacinia. Donec tempus tempus tellus, ac lacinia turpis mattis ac. Maecenas sit amet tellus nec mi gravida posuere non pretium magna. Aliquam nisl enim, tristique tempus placerat at, posuere in lectus. Aliquam tincidunt velit sit amet ante hendrerit tempus. Sed mauris arcu, aliquet ultrices malesuada sed, pretium id CTRL + V massa.

Section 2

Fusce ac sodales CSS magna. Donec et nisi dictum felis sollicitudin congue. Aliquam nisl enim, tristique tempus placerat at, posuere in lectus. Sed dapibus, lectus sit amet adipiscing egestas, mauris est viverra nibh, iaculis pretium sem orci aliquet mauris. Suspendisse potenti. Duis sagittis, est sit amet gravida tristique, purus lectus venenatis urna, id molestie magna risus ut nunc. Fusce ac sodales magna. Potenti et eros sed justo commodo bibendum non at nunc.

Suspendisse potenti cras molestie, risus a enim convallis vitae luctus libero lacinia. Nulla vel magna sit amet dui lobortis commodo vitae vel nulla. Suspendisse potenti. Sed dapibus, lectus sit amet adipiscing egestas, mauris est viverra nibh, iaculis pretium sem orci aliquet mauris. Donec a congue leo. Vestibulum sit amet ipsum lacus.

Section 3

Potenti et eros sed justo commodo bibendum non at nunc. Maecenas sit amet tellus nec mi gravida posuere non pretium magna. Nulla auctor eleifend turpis consequat pharetra.

Vestibulum sit amet ipsum lacus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed dapibus, lectus sit amet adipiscing egestas, mauris est viverra nibh, iaculis pretium sem orci aliquet mauris. Duis sagittis, est sit amet gravida tristique, purus lectus venenatis urna, id molestie magna risus ut nunc. Nulla vel magna sit amet dui lobortis commodo vitae vel nulla.

Suspendisse potenti. Aliquam nisl enim, tristique tempus placerat at, posuere in lectus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse potenti H20.

The HTML

(Progressively enhanced, so starting with a list of links to sections of content, with ARIA attributes and their attached styles loaded where environments permit)

This is before:

<ul>
 <li><a href="#section1">Section 1</a></li>
 <li><a href="#section2">Section 2</a></li>
 <li><a href="#section3">Section 3</a></li>
</ul>
<section id="section1">...</section>
<section id="section2">...</section>
<section id="section3">...</section>

This is after:

<ul role="tablist">
 <li role="presentation"><a href="#section1" tabindex="0" role="tab" aria-controls="panel1" aria-selected="true">Section 1</a></li>
 <li role="presentation"><a href="#section2" tabindex="-1" role="tab" aria-controls="panel2">Section 2</a></li>
 <li role="presentation"><a href="#section3" tabindex="-1" role="tab" aria-controls="panel2">Section 3</a></li>
</ul>
<section id="section1" role="tabpanel">...</section>
<section id="section2" role="tabpanel" aria-hidden="true">...</section>
<section id="section3" role="tabpanel" aria-hidden="true">...</section>

A few notes

I've commented up the script if you want to have a look. The trick is in allowing users to switch focus between the aria-selected tab and the content itself without having to cycle through all the irrelevant tabs. One changes focus betwen tabs using the and arrow keys and between the selected tab and the selected panel using the TAB key as normal. Pressing TAB will focus the first element inside the visible tab panel. Since this is a heading, "Heading level 3, section [1], tab panel should be announced".

When entering the tablist for the first time and focusing the leftmost tab, NVDA announces "tab control, [title], tab selected, one of three". Using the right arrow key () to choose the next tab, NVDA announces "[title], tab selected, two of three".

Alert! You're offline

A live region example which alerts the user whenever internet connectivity is lost or regained.

The HTML

(ARIA and ARIA-associated attributes displayed in green)

<div id="status" role="alert" aria-live="assertive" class="online">
	<p>You are online.</p>
</div>

A few notes

A test resource (test_resource.html) is polled every few seconds to see if it can be reached. If not, the XHR onerror changes the status message to "There's no internets. Go to the pub!", triggering the live region to announce this fact and suggestion. When connectivity comes back, the message should change back, triggering the live region again.

Warning dialog

Pressing the BIG RED BUTTON will open a <dialog> with a warning and a confirmation button. Closing the dialog returns focus to the BIG RED BUTTON that triggered it.

Note: Currently buggy in Chrome when used with NVDA. No NVDA issues with other browsers. Any suggestions greatly appreciated.

The HTML

We construct the dialog using an empty <dialog> element sat at the foot of the body element and two data attributes on the "trigger"; the red button, in this case.

The trigger

<button id="big-red" data-dialog-call="I really do not like you pressing that" data-dialog-response="I understand">Big Red Button</button>

The data-dialog-call attribute contains the warning and the data-dialog-response is the text for the close button. It defaults to "close" if no custom text is given.

The generated dialog markup

<dialog tabindex="0" open="open" role="alertdialog" aria-describedby="d-message">
   <div>
      <div>
         <p role="alertdialog"id="d-message">I really do not like you pressing that</p>
         <button>I understand</button>
      </div>
   </div>
</dialog>

Notes

Be sure to check out the little script which creates and handles the dialog. It…

  • Creates the dialog dynamically, adding the special alertdialog role reserved for warning and error dialogs
  • Makes the dialog focusable
  • Remembers the element that triggered the dialog
  • Injects the dialog message and close button based on the data attributes of the trigger
  • Associates the message with the <dialog> element itself with aria-describedby
  • Focuses the close button
  • Traps focus within the dialog
  • Moves focus back to the trigger element (the BIG RED BUTTON) on closing the dialog

Importantly, the script prevents users from interacting with the rest of the page until the dialog has been closed. We add visibility: hidden to the dialog's siblings for this and return focus to the dialog itself when TAB is hit on the close button. SHIFT + TAB still lets users escape the dialog to the browser address bar etc.

// Handle TAB
close.on('keydown', function(e) {
   if ((e.keyCode || e.which) == 9) {
      dialog.focus();    
      e.preventDefault(); 
   } 
});

On opening the dialog, NVDA reads "Dialog: I really do not like you pressing that; I understand (button)". Pressing TAB focuses the dialog itself to reread the message, in case you missed it. Pressing the close button closes the dialog and returns focus to the "trigger" element that invoked it. This causes NVDA to read "Practical ARIA Examples document, main landmark, big red button (button)".

A simple toolbar widget

A generic pattern for "toolbars" which offer buttons for manipulating content in some way. In this case, it's sorting a list.

Sorting crab names

  • Fiddler crab
  • Hermit crab
  • Red crab
  • Robber crab
  • Sponge crab
  • Yeti crab

The HTML

(ARIA and ARIA-related attributes in green as always)

<div role="toolbar" aria-label="sorting options" aria-controls="sortable">
  <button type="button" aria-pressed="true" data-sort="ascending">A to Z</button>
  <button type="button" aria-pressed="false" data-sort="descending">Z to A</button>
</div>
<ul id="sortable" tabindex="-1">
  <li>Fiddler crab</li>
  <li>Hermit crab</li>
  <li>Red crab</li>
  <li>Robber crab</li>
  <li>Sponge crab</li>
  <li>Yeti crab</li>
</ul>

A few notes

This is all about providing context. When the toolbar is entered (from either direction) and one of the buttons is focused, we are told that we are in a "toolbar", then the label of "sorting options" is also announced. Then, it is confirmed that the button is a "toggle button". This described behavior is in JAWS and NVDA. In JAWS 15 or NVDA with Firefox, a button with aria-pressed="true" will be appended with the word "pressed" when announced.

Chromevox announces aria-pressed="true" as "button pressed" and aria-pressed="false" as "button not pressed". This is a good reason to toggle the attribute value rather than removing aria-pressed as a boolean.

Update: This example now supports recommended keyboard navigation. That is, tabbing into the toolbar focuses the first button; using the left and right arrow keys switches focus between buttons; pressing TAB while focused on a button moves focus out of the toolbar (in this case, onto the list so it may be read).

Progressive Hamburger

From skip link to menu button, a progressive and accessible solution to "off canvas" navigation. View the demo (opens new tab). The trick is to provide the correct attributes and styles if javascript loads and to make sure the navigation region cannot be focused when it is off canvas. To do this, we must hide the navigation landmark with visibility : hidden. Because this removes the landmark from the landmarks list dialog, we must clone the navigation landmark so there is a visually hidden but accessible version at the bottom of the page.

The link/button HTML


/* Before js, the "hamburger" icon is just a link to the navigation landmark at the bottom of the page */

<a href="#navigation" title="navigation menu" aria-label="navigation menu">☰</a>

/* After js is loaded, we supply the hamburger with button semantics and aria-expanded info */

<a href="#navigation" title="navigation menu" aria-label="navigation menu" role="button" aria-controls="navigation" aria-expanded="false" >☰</a>

Some notes

When javascript loads, the navigation landmark is floated next to the <main> element and is pushed off-screen. The skip link ([href="#navigation"] in CSS) is given the button role, aria-controls="navigation" and aria-expanded="false". When focused using NVDA and Firefox at this stage, it reads "button collapsed navigation menu". When you press the button, "expand" is announced as aria-expanded is switched to true. A setTimeout function waits until the CSS transition has finished revealing the navigation menu, then makes the navigation focusable. Pressing TAB focuses the first menu item and the following (or an approximation of it) is read: "Navigation landmark, list, list item, home link." You are now free to tab through the navigation links.

Tabbing back onto the button announces "out of list, main landmark button expanded navigation menu". When TAB is pressed on the final navigation link, focus is programmatically returned to the navigation button again. You are then free to TAB through the links again or press the button to close the menu. Pressing SHIFT + TAB on the first navigation link takes you past the page content (next in order) to focus the navigation button too. So there is no keyboard trap, SHIFT + TAB on the navigation button itself takes you to the main content.

The demo is functionally similar in JAWS 15 + Firefox. It is currently unfunctional in Chromevox (keypress events on the button not accepted). Any help with this would be greatly appreciated.