Bruce Lawson's personal site

WordPress accessibility hacks

I really love WordPress (apart from a few minor niggles). It maintains all my links, archives, it’s customisable – it’s a splendid piece of software, and the fact it’s free is fantastic. WordPress conforms to the basic level of accessibility, but most WordPress blogs don’t pass all the WAI/ WCAG Accessibility guidelines, so I decided to hack around with the code to make it more accessible.

I use the default Kubrick theme (or the markup thereof – I’ve adapted my old pre-WordPress CSS to keep its look and feel). The code I’m mucking about with is all found in wp-content/themes/default. This is theme-dependent and withstands upgrades of the main guts of WordPress which are stored elsewhere. Some of the fixes I’d like to do need to be done to the inner bowels of the program, and would be overwritten on a reinstall or upgrade, even if I knew enough PHP to do them.

What’s the problem?

I’m not gonna pick on any one blog in particular, but out-of-the-box installs of WordPress 1.5 (Kubrick) fail thus:

There’s also some instances when the code could be made a lot easier for screenreader users, but which doesn’t fail a validator, but let’s sort out the WCAG violations first from easiest to hardest.

Defining the primary language of a document

I don’t know how useful this checkpoint is in the real world, but in the future, screenreaders will be clever enough to pronounce the text according to the language it’s written in. For example, the word “six” is pronounced differently if the document is written in English than if the document is written in French, even though both languages spell the word the same.

But to do this, it needs to be told which natural language you’re writing in. (“Natural” as opposed to computer languages) You specify this on the html tag, which is sent to the browser courtesy of our friend header.php.

After backing up your existing copy of the file, crossing your fingers and running thrice round your computer widdershins, change the html tag to read:

<html xmlns="" lang="en" xml:lang="en">

This is assuming you’re writing in English (that’s the “en” bits of the declaration). Should, for some odd reason, you wish to write in some other language, you’ll need to scour through the list of langauge codes. Or learn English, of course. (Yeah, I know that’s unfair, but some of my friends are English teachers and I don’t want them unemployed.)

Why do you specify your language twice for xhtml transitional? Because God tells you to, that’s why.

Save it and upload it and – Woo hoo! You rock!

Associate labels explicitly with their controls

This checkpoint is about forms, and associating the “prompt” (e.g.,”Enter your email address:”) with the actual field itself. It’s a hangover from ye badde olde days™ of table-based layouts, when the prompt for a form field might be in one column, miles away in markup terms from the actual form field in the next column.

The only forms that the site visitor encounters in the default WP1.5 installation are the comment form and the searchform. (You need to make WordPress comply with ATAG so that visually-impaired people can author with it. That’s for another post when I’m up to speed with ATAG. Maybe WordPress already does comply?)

Anyway, you need a label element that is explicitly associated with the input by means of an id. The jury is out on whether you should wrap the label around the input or not.

Enough philosophy; let’s get our mitts mucky. Find searchform.php. It’s a tiddler of a file, so you’ll have no trouble locating the search input field: it’s probably the second line. Look for the line that begins

<input name="s" type="text" id="s"

It’s already got an id of “s” (meaningful, hey?) so it’s easy to associate a label to it. But why would we need to have a label, except for box-ticking reasons? Surely, it’s obvious from the text on the submit button (“search”) what the field is?

That’s what I thought until I read the comment by Lachlan Hunt that labels shouldn’t be blank.

The problem is that a screenreader user wouldn’t know the purpose of the blank input field until after they’ve left it and heard the name of the submit button – and then would need to double back if they decided they wanted to do a search.

I know some people have a title on the input field – but screenreaders don’t read out the title by default, so I want a label on the the input field that is read out to a screenreader user, but doesn’t show on the screen and muck up the design.

Giving text to a screenreader but not the screen

Ordinarily, I’d accomplish this by “floating” the text off the screen using CSS – effectively, traditional image replacement, except without the background image. The screenreader would still read it out – it’s still in the source file – but it won’t be seen in a browser.

So, make a blank line of code immediately above the code for the input field above, and add

<label for="s"><span class="screenreader">Search this site</span><label>

Add this to your css so it’s off the screen for sighted users:

.screenreader {margin-left:-5000 em;}

Generally, this would enough to give text to a screen-reader while hiding it from the screen, but there seems to be a problem with IE/ Win, which is maybe caused by the fact that the label is intimately associated with the field that it labels.

Search box but with form field and submit button missingThis method works in Firefox but hides the entire search field in IE6/Win. Similarly, dispensing with the span and applying the screenreader class to the label itself removes the entire field – as does defining label {text-indent:-5000px;}.

Some research today shows that the following works in IE6/ Win, Firefox, Opera 8. It doesn’t hide the label text in IE5.5/Win. Can anyone report back about Mac browsers?

<label for="s" class="screenreader">Search this site</label>
with the CSS
.screenreader {display:block; height:0px; overflow:hidden; }

Phew! The reason this works in IE is that it doesn’t push the label text off the side of the screen (and thus drag the input field with it). Instead it hides the text by keeping it in the same place but forcing it to zero pixels high.

Comments form

Now it’s time to do the comments form. Obviously, you’re not using the pop-up comments form (right???), so fire up comments.php in your authoring editor of choice. Make sure you save a back-up copy!

There are four input fields in the comment form: name, email address, url and the actual comment box itself. The first three already have a label correctly associated, for example

<label for="url"><small>Website</small></label>

The trouble here is that the labels appear after the input fields, and as Gezmondo Citron points out,

prompts for form controls should be placed immediately to the left of the associated control, or immediately above the associated control.

This is easy to acheive; it’s a matter of cutting and pasting the labels to be above the input field in the source of comments.php. I made sure that the paragraph tags wrapped both the label and the input field, for extra semantic association between the two.

When this is achieved, you’ve got an aesthetic nightmare: because the labels are of varying lengths, the whole thing looks ragged and frankly vomitising:
ugly form with fields unaligned

Making the comments look vaguely appealing

There’s a few choices. We could make the labels on the left and the input fields on the right all align beautifully by using a table. Yes, you heard me right: a table! It’s perfectly legitimate in xhtml transitional, and perfectly allowable under WCAG. There’s also a reasonable argument to be made that a form is inherently tabular data.

However, I’m trying not to use tables ‘cos I’m the Purist From Hell™, so I’m going for the labels above the input field. I could force a line break between the label and input field by adding a <br /> between the two elements, but that’s not terribly semantic (although again, completely legitimate in xhtml trans).

Instead, I’m using css to tell the browser that a label should be displayed as if it were a block-level element. Labels are usually displayed in-line, but it’s entirely legitimate to use css to change that.

(A block-level element is displayed by default as occupying a line to itself – think of how a heading tag always starts a new line and has a “built-in” line-break after it, so anything following it also starts on a new line. Inline means the opposite: think of the default behaviour of an link in a paragraph; the link doesn’s start a new line, it appears in the text – hence “inline”.)

To do this, open your css file and add label {display: block;}.Simple as that.

Nearly done with this damn checkpoint

Finally, the comment entry box itself doesn’t have a label attached, but needs one for a screenreader user, so we’ll add an off-screen comment like we did for the search box. We’ll re-use the class .screenreader that we added to the CSS then. Find the line

<textarea name="comment" id="comment" cols="100%" rows="10" tabindex="4"></textarea>

and add this line immediately above:

<label for="comment" class="screenreader">Enter comment</label>

.. and Bob’s your Auntie’s secret toyboy.

Clearly identify the target of each link.

Rule 13.1.2 commands:

All Anchor elements are required not to use the same link text to refer to different resources.

This is A Big Deal™. You shouldn’t have two “read more” links that go to different places. If on any index or archive page, you two different posts both with “Read More” activated, you’re in contravention – as you’ve got identical link text to two different destinations.

Similarly with multi-post pages that have the same amount of comments: you might have “5 comments” for your post “Why I adore Bruce Lawson” and “5 comments” for your post “Bruce Lawson: brains and beauty” – with the same link text going to different places.

This is also A Bugger To Fix™. The “read more” function is dealt with by a WordPress function called the_content which is scary PHP. Fortunately, Derek Featherstone is much cleverer than me and he’s worked out what you need to change:

Uniquifying the “read more” links

Go to your index.php page and find the line <div class="entry">. Immediately below this is the call to the_content which gets your post and adds the “more” links as necessary. Comment it out – don’t delete it till you’ve tested the fix! – and add this line:

<?php the_content("Continue reading " . the_title('','',false),
0); ?>

This will change your more links to include the title of the post, thereby making them unique. Lovely!

The uniquification of the “x comments” link

I haven’t got a solution to this yet. The php that generates all this is immediately below the “read more” stuff in index.php and is also found in archive.php. It’s in the paragraph with the class of “postmetadata”. Here’s how the “1 comment” link is written out:

<?php comments_popup_link('No Comments &#187;', '1 Comment &#187;',
'% Comments &#187;'); ?>

We need to make it write out ‘1 comment on’+Post title (or “post slug”). That’s beyond my PHP skills however.

STOP PRESS: I’ve just found a post by Smiffy on how to change the core WP functionality to do this (note it’s therefore overwritten when you upgrade/ reinstall). I haven’t tried it yet, though …

Removing superluous fluff from being read aloud ….

Most people think of Accessibility™ as being the art of exposing everything to a screenreader, but it’s not that simple. If you’ve ever used a screenreader, you’ll know that there’s loads of extra structural information that the screenreader gives a user. Sometimes, there can be so much information that it becomes overwhelming, so part of being an Accessibility Ninja is reducing the amount of verbiage so the signal to noise ratio is better.

WordPress makes liberal use of the &#187; character “»” (also referred to as &raquo; – which is the same – and &laquo;). They appear the comments links above, and in the title of a page, as well as in the next and previous links in the individual post pages (single.php).

There’s a great post called “The Sound of the Accessible Title Tag Separator” which is canonical on how various printable characters sound in a screenreader.

“»” is all very decorative, but it’s read out by screenreaders as “right double angle bracket”, which is a pain in ears as well as the arse, especially when it has no real meaning – it’s purely WordPress eyecandy.

In the postmetadata links to comments, I just removed any reference to the character from archive.php and index.php. Now it just says “1 comment” rather than “1 comment »”, and I defy anyone to accuse me of philistinism here.

In single.php, the file that displays just the post and its comments, the sign is used instead of saying “previously” and “next”, so I just substituted the words instead:

<div class="alignleft"><?php previous_post('Previously: %','','yes')
<div class="alignright"><?php next_post(' Next: %','','yes')?>

As you’ve got those ids there, it’s easy enough to apply a background image with arrows to make it visually obvious what the links mean. (Note to self: must change presentational class and id names, too). I like the words “next” and “previously” there too, so a screenreader user understands what the links indicate.

The <title> is controlled by our old friend header.php. The line is probably line 7 in your editor, and it says

<title><?php bloginfo('name'); ?> <?php if ( is_single() ) { ?> &raquo;
Blog Archive <?php } ?> <?php wp_title(); ?></title>

Remove &raquo;, being very careful not to bugger up any of the surrounding PHP wizardry. I also removed the words “Blog archive” too.

There’s one last character pumped out by wp_title which the delightful Raena Armitage told me how to nuke. Instead of just having empty brackets as the argument for wptitle(), you can tell it the seperator you want by passing $sep=’blah’. For example, if you want a colon, use

<?php wp_title($sep = ':'); ?&g

My final title-producing code is thus:

<title><?php bloginfo('name'); ?> <?php if ( is_single() )
{ ?>&nbsp; <?php } ?> <?php wp_title($sep = ':'); ?></title>

Easy navigation between content and nav

The Kubrick theme produces the page in the header/ content/ navbar / footer order, which is cool for accessibility as your content comes first in tab order and reading order. Obviously, using Absolute Positioning you can do what I’ve done and visually position the sidebar on the left – that’s purely a design decision.

(If your theme has the sidebar before the content and you want to change it, it’s an easy fix: look in index.php for the line <?php get_sidebar(); ?>. Copy the whole line to be immediately below the line <?php get_header(); ?> and you’re sorted.)

However, content first is great – but what happens if someone wants to go the navigation bar? I’ve amended the code so at the bottom of every post is a link “Skip to Navigation”, which jumps to the sidebar.

Adding this link is easy-peasy (although not quite lemon-squeezy). We need to add a link inside “The Loop“, which is the main engine of your blog, looping through each post in turn and displaying it.

In index.php, look for the line <?php trackback_rdf(); ?> (it may be commented out). Below this line – but before the </div> which ends the div for this entry, add the following:

<p><a href="#sidebar">Back to navigation</a></p>

This link jumps to the element whose id is “sidebar” – which is the name of the navigation sidebar in the default theme. (Note as well that it’s the id on the sidebar’s surrounding div, and not an anchor tag, which means you won’t fall foul of the IE bug found by Jim Thatcher whereby the destination of a “skip link” must be in a table cell if it’s an empty named anchor).

So, you’ll have a link “Back to navigation” for every post on your blog’s page. Doesn’t this contravene checkpoint 13.1.2 that we’ve spent ages trying to follow for the “More” links?

Fret not, my dear chum. Each of the links goes to exactly the same place, which is perfectly fine.

And finally …

I’ve republished this post (first published 7 August 2005) as I think I’ve gone as far as I can by hacking the Kubrick theme. Anything else is the guts of WordPress which is overwritten when you upgrade or re-install. Hoepfully someone from the WP development team will see this and make a few tweaks to enhance the (already splendid) accessibility of the product.

I’m no PHP-wallah, so please let me know if my suggested fixes aren’t optimal.

Lastly, if you’re interested, and as clueless as I was before I started researching this (or just a lazy bugger) you might want a zip file of all the changed files I’ve made. If so, let me know.

Happy hacking!

(Last Updated on )

Buy "Calling For The Moon", my debut album of songs I wrote while living in Thailand, India, Turkey. (Only £2, on Bandcamp.)

65 Responses to “ WordPress accessibility hacks ”

Comment by Lachlan Hunt

There’s really no point to associating labels with form fields if the labels are empty. They just add supurfluous crud to the markup with zero benefit for anyone. However, it is better to label form fields, even if those labels are invisible for visual media.

With regard to the question of wrapping the label element around the form field, there’s really no semantic difference either way, only structural differences. Authors should be aware that IE only supports association using the for attribute, but it’s not always convenient to give an id to every field. Personally, I ignore the IE bug and wrap the label around the field when they’re next to each other in the source and use the for attribute when they’re seperated (such as in seperate table cells).

Comment by Bruce

That’s an interesting philosophical point, Lachlan: what to do when WCAG-mandated checkpoints seem wrong.

I’d recommended the empty label for a simple pragmatic reason. I’m doing some work for a big organisation that wants to be properly accessible, but also has a legal department that needs to feel confident that it could defend itself if it were ever taken to court for the web site.

We decided that we should adhere to all priority 1 and 2 checkpoints, as for better of for worse, the WCAG is the international standard. (We’re going for “WCAG 2+” compliance.) The UK DDA rightly doesn’t have a section508-style checklist, so the legal eagles feel happier following WCAG to the letter. I didn’t try to change their mind; the potential superfluity of labels doesn’t hinder accessibility.

You confirm my suspicion that a label should never be empty, anyway – it should always give information about what the field is for, even if that’s not needed for sighted visitors. I’ve updated the article to reflect that.

Personally, I agree with ignoring the IE bug in a personal site – it’s a personal site. For a corporate site, accommodating IE6 is an unfortunate fact of life. (I’ve high hopes for IE7, though).

Comment by Bruce

Hmmm .. I didn’t test my method in IE/ Win first, Jim (though it worked in Firefox). Doh! Updated the article – look for the elaborate overflow:hidden etc method of hiding the label text from the screen.

Comment by goodwitch

Awesome work Bruce…now the icing on the cake would be to get these changes into the next version of WordPress…or have you already accomplished that too? (looking behind your back to see if you are wearing a super hero cape).

Comment by Bruce

Genius Raena! Thank you!
With the addded stuff about making the comments form accessible, and your contribution, I think we’ve covered everything that can be achieved by hacking a WordPress theme, so I’m reoposting the whole post with today’s date.

Thanks to all who commented and emailed.

Comment by Raena Armitage

You’re welcome, Bruce!

Incidentally, you can use the same technique as you do on the unique read more link on the comments_popup_link part as well, to wit:

<?php comments_popup_link(__('No comments on ' . the_title('','',false)), __('One comment on ' . the_title('','',false)), __('% comments on '. the_title('','',false))); ?>

Looks ugly, but works.

Comment by João Craveiro

On label’s: it’s semantically incorrect to wrap the input field around with a label. The label (not the “label” tag, but the label that the “label” tag represents) ends before the input field.

As for the comment that said

there’s really no point to associating labels with form fields if the labels are empty.

you can just add class=”screenreader” to that label (which, by the way, only works fine if you don’t wrap the input within the label).

Finnaly, on uniquification of the “x comments” links, a slight change to Raena’s code:

<?php comments_popup_link(
__('No comments<span class="screenreader"> on' . the_title('','',false) . '</span>),
__('One comment<span class="screenreader"> on' . the_title('','',false) . '</span>),
__('% comments<span class="screenreader"> on'. the_title('','',false) . '</span>)); ?>

Comment by Jens Meiert

By the way – I just came across and had to gulp: Is WP really using “alignleft” and “alignright” class names (presumably not the only inadvisable identifiers)? Gah. Planning to use WP on my site, I now need to think about this since I do not want my inevitable WP modification efforts skyrocket.

Comment by Bruce

Well, the layout and markup is controlled by the theme which is infinitely customisable. The Kubrick theme uses those class names and a sprinkling of <small> tags, but there’s loads of different themes that use different markup. Check out the theme browser and examine the markup.

Comment by Aleksey Gureev

Bruce, though it’s a bit offtopic, but also has to deal about accessability and usability a bit. I saw your feed contains excerpts (summary) only ending with [..]. I didn’t find that convenient in WordPress as I didn’t control what’s going out to the feed and I hated that. So I created a small plugin to put only part before ‘more’ tag to the feed. Now I have fine looking and controlable feeds. 🙂

Comment by João Craveiro

Correct me if I’m wrong but … that’s not a plugin—it’s a hack. The purpose of plugins is that one (specially one that hasn’t got much technical skills) doesn’t have to edit core files.

Michael released K2 (Kubrick’s trone heir) Beta 1 today. 😉

Comment by Don't Forget Your Mouthpiece

New Look (Part Deux)

Ok it’s now definitely finished. Two acknowledgements must here be made:
Firstly Andy who helped me out with a CSS problem which was totally spooning up my pages in IE/Win.
Secondly, Bruce Lawson’s personal site has a couple of very use…

Comment by Frog Blogger

As a bona fide Mac user, I thought I’d offer a helping hand (for “hand“, read “glance at the left hand side of my screen“) with your Word Press accessification hacks.

Guess what? It works! You clever ol’ thing you!

Frog Blogger

Comment by Red rose ramblings

Accessible WordPress and FMC

I passionately believe that the Fulwood Methodist Church site should be as accessible as possible to all. Jesus, after all, never told his disciples to go out and preach the good news to just the sighted and the user of Internet Explorer. I’ve r…

Comment by Sergio Pinna


I’m found that PHP (K2 V0.9):

‘.__(‘Pages:’,’k2_domain’).’ ‘, ”, ‘number’); ?>

it’s better style then

…all that are in my opinion, ok.. 🙂

Try it!
P.S. I’m sorry for my english.

Comment by Bruce Lawson personal site WordPress accessibility hacks | fix my credit

[…] Bruce Lawson personal site WordPress accessibility hacks Posted by root 1 hour 26 minutes ago ( That what i thought until i read the comment by lachlan hunt that i didn 39 t find that convenient in wordpress as i didn 39 t control what going of the site so that it is now powered by wordpress and was pleased Discuss  |  Bury |  News | Bruce Lawson personal site WordPress accessibility hacks […]

Comment by check up

Most blogs don’t conform to WAI/WCAG standards in terms of accessibility. Learn how to meet these standards with Bruce Lawson’s accessibility hacks

Comment by ladies gym bag nike

Hello there! This is the second time visiting now
and I personally just wanted to say I truley get pleasure from reading your weblog.
I’ve decided to bookmark it at with the title: Bruce Lawsons personal site : WordPress accessibility hacks and your Domain name: I hope this is fine with you, I’m attempting to give
your fantastic blog a bit more visibility. Be back shortly.

Leave a Reply

HTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> . To display code, manually escape it.