Accessible UI buttons

UI buttons can pose something of a problem. They are often designed as cute little images and often come in sets, like the following:
a controlset containing 3 buttons: previous section, start slideshow and next section. Follow link to example implementation

The problem lies in that there are many requirements for buttons so they are easy to skin, don’t slow page performance and remain accessible:

  • Most basically, the button image must be visible (to most users)
  • Support for users with images disabled (some kind of text or symbol so there is an indication of what each button represents)
  • Full meaning of button available as a title attribute (provides WCAG 2.0 conformance and a tooltip in most browsers)
  • Full meaning of button rendered in assistive technologies (AT) (e.g: screen readers) even if title attribute is not supported
  • Ability to reskin the entire UI using CSS alone and ability to combine all images into a single sprite (i.e: CSS image-replacement)

A solution

This solution attempts to meet all of the requirements above by combining a number of techniques including accessible image replacement and image sprites.


The following HTML sets the base for all of the requirements above:

<ul class="controlset" role="controlset">
	<li class="previous">
		<a href="#" role="button" title="Previous section">
			<img class="for-css-replacement" src="displacement.png" alt="" />
			<span class="button-symbol">&lt;&lt;</span>
			<span class="button-title">Previous section</span>
	<li class="play">
		<a href="#" role="button" title="Start slideshow">
			<img class="for-css-replacement" src="displacement.png" alt="" />
			<span class="button-symbol">|&gt;</span>
			<span class="button-title">Start slideshow</span>
	<li class="next">
		<a href="#" role="button" title="Next section">
			<img class="for-css-replacement" src="displacement.png" alt="" />
			<span class="button-symbol">&gt;&gt;</span>
			<span class="button-title">Next section</span>

Break it down

  • The class of controlset is placed on the buttons’ container so it can be styled. The role of controlset (from ARIA) identifies the purpose of this element to AT
  • The classes on the list items (e.g: <li class="previous">) can be used by JavaScript to apply actions to the buttons
  • a elements are used for the actual button because they are automatically focusable in all browsers. The role of button clarifies that this is not a link.
  • A title attribute is used on the a element to act as a tooltip expansion of the meaning of the button.
  • A displacement image is used to support accessible image replacement.
  • button-symbol spans (e.g: <span class="button-symbol">) are used to contain a short symbolic text representation of the image (ASCII art). This symbol fits within the space left by the button image if images are disabled by the current user.
  • button-title spans (e.g: <span class="button-title">) are used to contain a text representation of the meaning of the button for AT that don’t support the title attribute.


The following CSS is used to ensure parts the buttons are presented to all users appropriately:

.controlset {
	background: #dcdcdc; /* A neutral colour for the bar */
	margin: 0; /* Remove default spacing */
	padding: .3em; /* Set some padding */
	list-style: none; /* Remove bullets */
	overflow: hidden; /* Ensure controlset encloses floats within */
	width: 30em; /* optionally, set a width */
.controlset li {
	float: left; /* Float each button so they appear in a line */
	padding-right: .2em; /* Give some space between buttons */
.controlset li a {
	display: block; /* Display buttons as a block level element */
	width: 47px; /* Give the clickable region a width and height */
	height: 36px;
	overflow: hidden; /* Ensure anything outside the dimensions is clipped (visually hidden) */
	background: #bdbdbd url(buttons-sprite.png) left top no-repeat; /* Associate the sprite image with buttons */
	text-decoration: none; /* Default text-style for users with images disabled */
	color: black;
	font-size: 160%;
	text-align: center;
.controlset li a span.button-symbol {
	speak: none; /* Ensure this ascii art symbol is not spoken by screen readers */
.controlset li a img.for-css-replacement {
	width: 47px; /* Give the displacement image dimensions */
	height: 36px;
	border: none; /* Ensure this element doesn't get the defaul linked image border */
.controlset li a span.button-title {
	position: absolute; /* Send the title text off-left so it is heard but not seen */
	left: -9999px;

/* Position the sprite image for different buttons and different states */
.controlset li.previous a {
	background-position: left top;
.controlset li.previous a:hover {
	background-color: #afd3ad; /* Set a background colour for users with images disabled */
	background-position: left -36px;
.controlset li.previous a.pressed {
	color: #207a00; /* Set a text colour and outline for users with images disabled */
	outline: 2px solid #207a00;
	background-position: left -72px;

.controlset a {
	background-position: -47px top;
.controlset a:hover {
	background-color: #afd3ad;
	background-position: -47px -36px;
.controlset a.pressed {
	color: #207a00;
	outline: 2px solid #207a00;
	background-position: -47px -72px;

.controlset a {
	background-position: -94px top;
.controlset a:hover {
	background-color: #afd3ad;
	background-position: -94px -36px;
.controlset a.pressed {
	color: #207a00;
	outline: 2px solid #207a00;
	background-position: -94px -72px;

Most importantly

The key CSS above was:

  • The button-symbol class is told to speak: none; so that screen readers don’t attempt to make sense of the ASCII art.
  • The button-title class is sent off-left as this is the most reliable method to ensure text is not visible but still available to AT that don’t support the title attribute.
  • The img.for-css-replacement is given the same dimensions as the button so that the button-symbol element is pushed outside the button’s clipping area while images are enabled. If images are disabled, the button-symbol will fall back into view.
  • The background image on the buttons is pushed around using CSS so that different parts of the sprite image are exposed for different buttons and different button states (like hover or pressed states).


JavaScript can be used to give these buttons functionality. Because these buttons are non-functional without JavaScript, all of this HTML should be added to the DOM of the page you are working on using JavaScript. That way, users without JavaScript will not be confused by a non-functioning UI.

Working example

A complete example of accessible UI buttons is available.


The one downside of this technique is that in browsers that don’t zoom, the button size cannot be increased without using assistive technology. In other words, the button size won’t increase proportionate to text size. This issue can be somewhat mitigated by setting the UI element dimensions in em units. Unfortunately this would make using sprites to increase performance almost impossible as a larger than normal button would expose multiple UI elements on the sprite image.

A good compromise is to ensure your UI buttons are not too small to begin with, also nice largish buttons make your UI easier for everyone to use!

Posted in

Leave a reply