Making Drupal 7 more accessible

Here at Nomensa we aim to make everything accessible, whatever we do. Whilst we are big fans of Drupal, there's a few issues with some of the markup that comes out at the front end, especially from an accessibility point of view

We're not just referring to the sometimes rather enthusiastic use of <div> wrappers, but also the issues with labels and <span> elements being absent or in the wrong place, missing ARIA roles and attributes, etc. As we aim to deliver all our projects to WCAG2.0 AA standards at least, these issues arise on all our build projects, and historically there has been a tendency to facilitate these corrections by using the power of the theming layer.

It would be helpful however if we only had to fix these things only once, rather than fixing them over and over again for every project.

Luckily we are maintaining a Sandbox Drupal version which we use as a base for all our Drupal build projects. This contains all the modules we use on every build project as well as our very own module, along with install profiles to enable these modules.

As we use this for all our projects we should be able to resolve these front-end issues once in code, and enjoy the benefits for as long as we use D7.

So what was the challenged we faced? Let's use the breadcrumb as an example.

You would think that something as simple as the humble breadcrumb would not pose much of a problem when it comes to accessibility, but this wasn't the case. By default the HTML for the breadcrumb consists of:

<h2 class="element-invisible">You are here</h2>
    <div class="breadcrumb">Home</div>

To make the breadcrumb more accessible, the HTML for it should look more like the following:

<nav aria-labelledby="breadcrumb_title" class="breadcrumb" role="navigation">
<p class="hide breadcrumb_title">You are here</p>
  <ul class="breadcrumb">
    <li>Home</li>
  </ul>
</nav>

To make matters more complicated, not only could the HTML do with updating, it is also hard coded into the theme_breadcrumb function in includes/theme.inc, which is part of Drupal core.

function theme_breadcrumb($variables) {
  $breadcrumb = $variables['breadcrumb'];

  if (!empty($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">' . t('You are here') . '</h2>';

    $output .= '<div class="breadcrumb">' . implode(' » ', $breadcrumb) . '</div>';
    return $output;
  }
}

As we all know, Rule 1 of Drupal development says "DO NOT HACK CORE!!", hence we're not just going to change it in the core files, no matter how trivial that might be.

Which leaves us with a interesting challenge; How to alter the breadcrumb to match what we need, and move the HTML from the core into a template, so it can easily amended by a front-end developer if need be. This is where Drupal's hook system comes into play. There is a large number of exposed hooks, and a large part of our jobs as Drupal developers is tracking down the right hook to use for what we're trying to achieve...

There are hooks available at various levels, such as hook_menu_breadcrumb_alter() which allows you to alter the elements in the breadcrumb, but unfortunately not the HTML used for theming it. For this we need to go even lower down, to the core theming layer of Drupal itself. This exposes a hook named hook_theme_registry_alter(), which like it would suggest, allows you to alter the registry in which all the theme functions are contained.

So this allows us to do something like this in our own module:

function nomensa_theme_registry_alter(&$theme_registry) {

    foreach ($theme_registry as $key => $element) {
      if ( 'theme_breadcrumb' == $theme_registry[$key]['function']) {
        $theme_registry[$key]['function'] = 'nomensa_theme_breadcrumb;
      }
    } 
}

This will change the theme registry to use our function for theming the breadcrumb, rather than the core function. Now in our own module, we can alter the HTML:

function nomensa_theme_breadcrumb($variables) {
 
  $breadcrumb = $variables['breadcrumb'];

  if (!empty($breadcrumb)) {
   
    $output = '<nav aria-labelledby="breadcrumb_title" class="breadcrumb"
          role="navigation">
      <p class="hide breadcrumb_title">' . t('You are here') . '</p>';
    $output .= '<ul class="breadcrumb"><li>' . implode(' </li><li> ', $breadcrumb) 
            . '</li></ul></nav>';
    return $output;
  }
}

This will now output the desired HTML, but the markup is still in the module. The next step is to create a template file, and make the function use that.
To do this we create a file called breadcrumb.tpl.php which goes in a template/ directory inside our module, which contains the markup:

<nav aria-labelledby="breadcrumb_title" class="breadcrumb" role="navigation">
<h2 class="hide" id="breadcrumb_title"><?php echo t('You are here'); ?> </h2>
  <ul class="breadcrumb">
    <li><?php echo implode('</li><li> ', $variables['breadcrumb']); ?></li>
  </ul>
</nav>

And then it is just a question of amending our new theming function to use this template:

function nomensa_theme_breadcrumb($variables) {
 
  $breadcrumb = $variables['breadcrumb'];

  if (!empty($breadcrumb)) {
   
    $template_file = drupal_get_path('module', 'nomensa').'/templates/breadcrumb.tpl.php';

    return theme_render_template($template_file, $variables);
  }
}

We use the same mechanism for other elements, such as menu, button, messages, input, etc. To faciliate this we rewrote the hook_theme_registry_alter implementation as follows:

function nomensa_theme_registry_alter(&$theme_registry) {

  // For each of the registry entries, check if there is a 
  // function in the nomensa module with the same name, and use that instead
  foreach ($theme_registry as $key => $element) {

    if (function_exists('nomensa_theme_'.$key) 
       && $theme_registry[$key]['function'] != 'nomensa_theme_'.$key) {
      $theme_registry[$key]['function'] = 'nomensa_theme_'.$key;
    }
  }

}

This will now simply check whether there's a function with a prefix of nomensa_theme_ for any entry in the registry, and use that instead. So we can now just create nomensa_theme_breadcrumb, nomensa_theme_menu_tree, etc and it will just work. And using the mechanisms detailed above we can extract all the HTML into a template, which means we can alter it to suit our requirements.

Mission accomplished!