Skip to main content
ocean waves

Blog Post

Accessibility in Drupal 7, A Case Study

In October, 2015, a team from Mediacurrent worked on an accessibility project for the Knowbility Open Air hackathon. See Part 1 for background information. We built the Grey Muzzle Organization website to be a shining example of an accessible Drupal site. Following are many of the things we did to accomplish this.  

Colors

Colors for the website were chosen early on with accessibility in mind. Since one out of every twelve people suffers from some sort of colorblindness and many others have various forms of low vision, careful thought was given to the color palette. Two design concepts were provided for the client to choose from, each included their own four color palette. The colors were selected with consideration to hue, saturation and lightness. Particular attention was given to the darkest color, since it would be used for the typography and needed good contrast to ensure optimal readability. Overall contrast was achieved through the use of light vs. dark, the use of complementary colors (the opposite side on the color wheel) and included both cool and warm colors. After the colors were selected, they were tested using the color contrast check tool provided by Jonathan Snook

(See Accessible Color Swatches for additional information.)

Two color palettes of 4 colors each

 

Theme

After having tested the most used Drupal base themes for accessibility earlier in the year and finding them all adequate as an accessible base with none really standing out above the others, in the interest of time, we chose to go with the one we were most familiar with as a team, the Omega theme.

 

Contrib Modules

The modules we chose specifically for accessibility were:

Accessible Mega Menu for the main menu of the site.  After first evaluating half a dozen menu modules for accessibility and then a failed attempt at creating a module that uses Dylan Barrell’s WAI-ARIA menu from his A11ify library, the Accessible Mega Menu module proved to be a good compromise.  This module integrates Adobe’s Accessible Mega Menu (no relation to Drupal’s Mega Menu module) into Drupal.  It didn’t have all the features of a WAI-ARIA menu but did allow a person to tab through just the parent level menu items and expand and access the child level items only when desired (as opposed to forcing keyboard-only users to tab through every single menu item).

Text Resize for text resize capabilities.  Browser zoom is better than nothing but it often adds a horizontal scroll to a page that is difficult for users with low vision.  We used Text Resize for an on-page control to allow a person to increase just the text size of the page.

Add to Any social share buttons.  We tried other major social share buttons and none were remotely accessible without a major rewrite. Either the buttons weren’t navigable with a keyboard or the popup windows weren’t accessible or a combination of these. Add to Any was the clear front runner in this arena.

Accessible Forms was a great find that helped with better accessibility in Drupal forms, which was a challenging part of the project.

 

Style Guide Driven Design

Our goal was to have clean and semantic markup as much as possible...which can be a bit of a challenge with Drupal’s propensity for div-itis. Our general process was for Mario to create the markup for a particular component inside of a KSS-Node style guide and then using various methods, get Drupal to output that component using that markup.

 

Overriding Drupal’s Default Output

To get Drupal to output the clean markup that Mario created for us, we used various techniques to modify the default Drupal output:

Overriding tpl.php (pronounced: tippel-fip!) Files

For instance, we wanted images to be wrapped in a <figure> element and if there was a caption, for it to be a sibling of the image and wrapped in <figcaption> tags. Much more semantic than the default drupal divs.

<figure>
 <img src=”bla.jpg”alt=”bla bla”>
 <figcaption>My excellent image</figcaption>
</figure>


In order to accomplish this we:

  1. Changed the file--image.tpl.php’s main wrapping element from div to figure
  2. Then in the tpl.php for the caption field, wrapped the field in a figcaption element:

field--field-caption.tpl.php file:

<?php foreach ($items as $delta => $item): ?>
 <?php if (isset($item['#markup']) && !empty($item['#markup'])): ?>
   <figcaption class="field-item <?php print $delta % 2 ? 'odd' : 'even'; ?>"<?php print $item_attributes[$delta]; ?>><?php print render($item); ?></figcaption>
 <?php endif ?>
<?php endforeach; ?>

Our resulting HTML looked like this:

<figure class="media__image">
 <img typeof="foaf:Image" src="http://dev.greymuzzle.org/sites/default/files/styles/medium/public/always_in_my_heart/Zeus.jpg?itok=yr9QTO14" width="220" height="220" alt="">
 <figcaption class="dog-name">Zeus</figcaption>
</figure>
 

Overriding Views Output

We overrode views output by excluding fields from display and then adding markup with the appropriate fields to one custom text field.

Example: For our Always in My Heart list page, we excluded all fields from display, grabbed Mario’s semantic markup from the styleguide with the classes he’d assigned and then in a custom text field, added the markup and substituted in the field tokens in the appropriate places:

<div class="media-wrapper">
  <figure class="media__image">
    [field_heart_image]
    <figcaption class="dog-name">[field_heart_dogs_name]</figcaption>
  </figure>
</div><!-- /media-wrapper -->

<div class="donor-wrapper">
  <h3 class="donor__name">[title]</h3>
  <div class="donor__text">
    [body]
  </div>
</div>

This gave us the output

<span class="field-content">
  <div class="media-wrapper">     <figure class="media__image">       <img typeof="foaf:Image" src="http://greymuzzle.org/sites/default/files/styles/medium/public/always_in_my_heart/Lewis.jpg?itok=7wF62frk" width="220" height="220" alt="">       <figcaption class="dog-name">Lewis</figcaption>     </figure>   </div>   <div class="donor-wrapper">     <h3 class="donor__name">Karen and Vicki</h3>     <div class="donor__text">       <p>Dogs come into our lives to teach us about love, they depart to teach us about loss. A new dog never replaces an old dog, it merely expands the heart. If you have loved many dogs, your heart is very big.</p>     </div>   </div> </span>

Overriding Theme Functions

We wanted breadcrumbs to be available to screen readers as navigation elements so we used the THEME_breadcrumb function to wrap the breadcrumbs in a nav element and provide a label for it.  Providing the label as a hidden h2 also made the breadcrumbs more accessible to screen readers as the label would show up in the headings list as well as the nav in the landmarks list.

function greymuzzle_breadcrumb($variables) {
 $breadcrumb = $variables['breadcrumb'];
 $title = strip_tags(end($variables['breadcrumb']));
 
 if (!empty($breadcrumb)) {
   array_pop($breadcrumb);
   // Provide a navigational heading to give context for breadcrumb links to
   // screen-reader users. Make the heading invisible with .element-invisible.
   $output = '<h2 class="element-invisible" id="breadcrumbs">' . t('You are here') . '</h2>';
   $output .= '<nav aria-labelledby="breadcrumbs">';
   $output .= '<div class="breadcrumb">';
   $output .= implode('<span aria-hidden="true"> / </span>', $breadcrumb);
   $output .= ' / ' . $title . '</div>';
   $output .= '</nav>';
   return $output;
 }

 

What We Let Drupal Do and Then Modified

Labeling Menu Blocks

One of the ways in which a screen reader user navigates websites is through the HTML5 structural elements such as header, footer, navigation, aside, etc.  The screen reader allows them to bring up a list of the structural elements on a page and jump directly to the different sections.  Because of this, it’s always a good idea to mark up a list of links that would be considered navigation with the nav element.  This way, a user can quickly jump to the navigation on the site.  It’s also a good idea to label these nav elements so that the user knows what kind of navigation to expect.  For example, a main navigation, a section navigation, footer navigation, etc.  The label of the navigation will be read to the user along with the word “navigation”.  In order to get Drupal to output the menu links in this way, we need to do three things:

  1. We used the menu_block module in order to create section menus from the subsections of the main menu.  This gave us the menu-block-wrapper.tpl.php template where we changed the wrapper div to a wrapper nav.  We also did this in the block.tpl.php file for the main menu because the main menu didn’t use the menu_block module.
  2. Next, we made sure the menus were labelled with an h2 which was hidden with the element-invisible class.  This gave us our label that would be read along with the “navigation” by the screen reader.
  3. Finally, we needed to associate the nav element with the h2 label.  We did this by adding an id to the h2 and then referencing that id in the aria-labelledby attribute of the nav element.   

In all, it looked like this:

Menu-block-wrapper.tpl.php - used for section navs which pulled appropriate sections from the main menu.  

<nav class="<?php print $classes; ?> sidebar-nav__wrapper" aria-labelledby="aria-label-block-menu-block-<?php print $config['delta']; ?>">
 <?php print render($content); ?>
</nav>

 Block--accessible-mega-menu--main-menu.tpl.php - this was the tpl.php used for the main menu of the site.  It does the same thing as the menu-block-wrapper.tpl.php file above but required a separate tpl.php file since the main menu did not use the menu_block module.

 

<div<?php print $attributes; ?>>
 <?php print render($title_prefix); ?>
 <?php if ($block->subject): ?>
   <h2 class="element-invisible"<?php print $title_attributes; ?>><?php print $block->subject; ?></h2>
 <?php endif; ?>
 <?php print render($title_suffix); ?>
 <nav<?php print $content_attributes; ?>>
   <?php print $content; ?>
 </nav>
</div>

 Block preprocess function - this was where we added the id to the h2 and the aria-labelledby attribute which were used to label the navigation:

function greymuzzle_preprocess_block(&$variables) {
  // If this is a menu_block, we need to:   // 1. unset the role of navigation as we have a separate nav element   // 2. Set an id to be used by the aria-labelledby in the nav element   if ($variables['block']->module == 'menu_block' &&        isset($variables['attributes_array']['role']) &&        $variables['attributes_array']['role'] == 'navigation') {     unset($variables['attributes_array']['role']);     $variables['title_attributes_array']['id'][] = 'aria-label-block-menu-block-' . $variables['elements']['#config']['delta'];   }   // Add accessibility to main menu block   if ($variables['block_html_id'] == 'block-accessible-mega-menu-main-menu') {     $arialabel = 'aria-label-' . $variables['block_html_id'];     $variables['title_attributes_array']['id'] = $arialabel;     $variables['content_attributes_array']['aria-labelledby'][] = $arialabel;   } }

 This resulted in the following html for the section menus.  Notice the id attribute in the h2, and the aria-labelledby attribute in the nav element which references the h2’s id: 

<div id="block-menu-block-1" class="block block--menu-block block--menu-block-1">
 <h2 class="block__title" id="aria-label-block-menu-block-1">About Us</h2>
 <nav class="menu-block-wrapper menu-block-1 menu-name-main-menu parent-mlid-0 menu-level-2 sidebar-nav__wrapper" aria-labelledby="aria-label-block-menu-block-1">
 <ul class="menu"><li class="first leaf active-trail active menu-mlid-1431"><a href="/about-us/vision-mission" class="active-trail active">Vision &amp; Mission</a></li>
<li class="leaf menu-mlid-868"><a href="/about-us/what-we-do-and-why">What We Do &amp; Why</a></li>
<li class="leaf menu-mlid-1296"><a href="/about-us/who-we-are" title="">Who We Are</a></li>
<li class="leaf menu-mlid-3231"><a href="/about-us/who-we-help" title="">Who We Help</a></li>
<li class="leaf menu-mlid-872"><a href="/about-us/general-faqs" title="">General FAQs</a></li>
<li class="leaf menu-mlid-1444"><a href="/about-us/financials-and-annual-report">Financials and Annual Report</a></li>
<li class="last leaf menu-mlid-6280"><a href="/our-volunteers">Our Volunteers</a></li>
</ul></nav>
</div>

Keyboard Navigation

Keyboard navigation was an important aspect of accessibility of the project as it affects 2 major groups of disability categories - blind and mobility impaired.  During the mockups and theming work, a logical source order (and therefore tabbing order) was established early on for accessing various pieces of the page and at various display widths.  We also made sure that the item receiving current focus was always obvious across all major browsers with a blue border. In addition, we took care of removing any rogue hidden headings and links that always seem to crop into Drupal themes.

Forms

All form controls were given some sort of accessible name, whether it be an actual visual label for form fields in the contact form, a hidden label for the search form in the header (but still available to screen readers), or through the use of aria-label and aria-labelledby.

The Accessible Forms module was a nice addition for form accessibility.  It did 3 things for us:

  1. Added the HTML5 required attribute to required fields so that a user would be alerted that a field is required
  2. Added “(required)” to the visible label for required fields (instead of asterix *) - not all screen readers treat asterisks the same.  Some just read asterisk/star and some ignore it altogether, so using it alone to indicate required fields is not reliable.  
  3. Added aria-invalid to form fields with errors making it easier for screen readers to know which form fields need to be corrected.

In addition to the improvements that Accessible Forms’ provides, we also applied a core patch. This gave us two additional benefits:

  1. When Drupal core adds the required attribute, it sets it to “true” which is incorrect.  According to W3C guidelines:

    “The presence of a boolean attribute on an element represents the true value, and the absence of the attribute represents the false value. The values “true” and “false” are not allowed on boolean attributes. To represent a false value, the attribute has to be omitted altogether.”

    This means the value for the required attribute either shouldn’t be set or if it is, it’s set to “required” instead of “true”.  
     
  2. The patch also sets the attribute of aria-required to “true” for required fields.  Because of cross-browser and browser/screen reader inconsistencies, both are necessary for required fields.  See John Foliot's post for a more in-depth discussion on the topic.

The next task was to make form errors more accessible.  When a user submits a form, and there is an error, the form gets reloaded and the list of errors sits above the form.  But a screen reader user has no way of knowing that and short of reading through the page, has no way of finding the errors.  We added role=”alert” to the div wrapping the error messages and altered the title of the page to indicate there was a problem with form submission.  When a page is loaded, the first thing that is read by the screen reader is the page title, so noting in the title that there was a problem would provide instant feedback. It was a bit of a hack but given the time constraints and limitations of Drupal in this area, it got the job done.

We also made it more visually apparent which fields were in error.  By default, this is done mainly adding a thin, colored border to the form control.  It needed to be very apparent without the use of color for people who are color blind.  We opted to make the colored border 5px in width in so it would stand out if the color was not apparent.

On the advanced search form where you can choose what content types to search, the checkboxes needed to be grouped by wrapping them in a fieldset and the label in a legend tag so that it was clear to screen reader users that they were related and what they were for.

The accessibility toolbar

See the blog post on building the accessibility toolbar.

Alt texts

Alt texts were given a great amount of attention on the site since there were so many images in different contexts - images with and without captions, images that were purely decorative, images with text embedded in them, linked images, etc. Each of these scenarios requires different alt text. See Describing Images for Improved Web Accessibility for more information.

QA

Our final task as we were finishing up the site was to do quality assurance testing on the content as the content itself would also be judged for accessibility in the hackathon. This was done with the following in mind (not all are specifically accessibility concerns but all items can affect accessibility):

  • Make sure all links are either relative (no hard-coded domain name in links) or 3rd party site since this site would be sitting at several different domain names before it’s final launch to production.
  • Make sure links work.
  • Make sure no links open into new tab or browser window unless it was noted in the link text. Opening pages in new windows/tabs can disorient users who are blind. It is better to open them in the same tab since users always have the choice in opening them in a new tab if they prefer. Forcing a new tab removes the ability for that choice.
  • Make sure there are no raw URLs or email addresses used as the link text (with a few exceptions).
  • All generic link text (read more, click here) has hidden context or is changed to better link text.
    <a href=”my-link.html”>Read more <span class=”element-invisible”>about Grey Muzzle grants</span></a>
  • Proofread for typos and grammar.
  • Make sure page urls are friendly so that if an outside site links to a page with generic text, a screen reader user can get an idea of where they’re going by the url.
  • Check that breadcrumbs are correct.
  • Donate buttons/links have hidden text (unique to this project): The third party donation vendor that was being used by the organization had a website that was very inaccessible. Changing them over to an accessible vendor in the timespan we had for the competition was not feasible. Instead, any time we linked to the donation site, we included hidden text for screen reader users to let them know that the link was inaccessible and that they needed to contact Grey Muzzle directly in order to make a donation.

Summary

I was proud of what we created - an accessible site with an attractive design. There was definitely still room for improvement though as there were a few things that Drupal just made it really difficult to do. It will be exciting to work in Drupal 8 for the 2016 hackathon. Drupal 8 is more accessible out of the box, fixing some of the difficult issues we encountered in Drupal 7, and doing many of the things listed above by default. Time to start preparing for the 2016 hackathon!

Additional Resources
Accessibility Hackathon, Part 1 | Blog Post
Building an Accessibility Toolbar with Drupal | Blog Post
Accessibility Best Practices for Content Editors | eBook