Usable Category Navigation in WordPress Pages

February 15, 2007

Topics: Accessibility, Blogging, Usability.

Wordpress isn’t really designed to be used as a CMS (Content Management System). It’s designed for blogging – nonetheless, it’s a surprisingly powerful little content management system, and with a little bit of tweaking you can pretty easily turn it into a nicely flexible system.

One of the challenges that needs to be dealt with is the use of Pages. A “Page,” in WordPress parlance, is a document which sits outside the blog chronology. For the purpose of a CMS, you don’t really want all of your posts to be chronologically organized. You could rewrite the Post templates and the permalink format to eliminate all date information, which would provide you with a nice CMS-like organization – but would also eliminate the blog function. Realistically, you probably want both.

It’s very easy to create a large number of WordPress pages and sort them into a hierarchy. For presentation, the default WordPress function wp_list_pages creates a very nice nested group of unordered lists. This can be styled using CSS (Cascading Style Sheets) to create either a long list of pages with inset sub pages, or a fancy CSS-driven set of drop down or flyout menus.

So far, so good. If you’ve got 10 pages, a single list is fine. But for larger sites, you’re running into other challenges. A straight list of your 150 pages is ugly, difficult to use, and difficult to navigate.

But the classic drop down or flyout menu has some usability issues. For people with mobility impairments, for example, the ability to use a mouse may be limited – and it takes a fair amount of core source hacking in order to attach the javascript needed to make these menus completely keyboard navigable. It’s easy to make certain that the top level link in each category is usable from the keyboard – so your responsibility is to make certain that the top level category link will provide access to all sub pages.

Again, there’s an easy solution: write links to all your further pages into your document content.

Ick. This solution sucks. First of all, if you’re creating this site for a client, you can’t necessarily rely on them to retain the content you’ve carefully created. Second, it’s very awkward to have to hand-maintain links to every page on the site. Why are you using a CMS if you’re going to have to do this anyhow?

Thankfully, although it’s not immediately obvious, WordPress does provide a way to access the children of pages programmatically. Using this code, you can simply create a secondary navigation section which provides easily keyboard-navigable links to all pages below those top level documents.

The Code

post->ID, 'category', true); ?>

post_parent) != the_title('' ,'',false)) { echo "
    "; wp_list_pages("child_of=".$post->post_parent."&title_li="); echo "
"; } if(wp_list_pages("child_of=".$post->ID."&echo=0")) { echo "
    "; wp_list_pages("title_li=&child_of=".$post->ID."&sort_column=menu_order"); echo "
;"; } ?>

What’s going on here?

First up has really nothing to do with the navigation menu itself. This is a header for the category navigation menu, arbitrarily placed in a level 3 heading element. It’s a little oddly phrased – this is because WordPress doesn’t provide access to Category labels for Pages the same way they do for Posts. Therefore, this is actually sourcing a Custom Field – a feature available in WordPress which gives you the ability to attahc any custom information to a document. In this case, I’ve created a custom field named “category” which contains the phrase I wish to be considered the category for this item. Generally, it’s the link text of the top level link, although you could set it to be anything you wish.

We’re trying to talk about usability, however, so I’m inclined to suggest sticking to something appropriate.

Note that the code refers to the variables $post and $post_parent. It’s important to know that “Page” is a formality which indicates a post which resides outside the chronology – from a database management perspective, everything is a post.

The second block of code is a tricky bit. This if query is checking to see whether the current page is a subpage of any other page. In general, WordPress has great built-in conditional functions – you can very easily check whether something is a page (is_page() or whether it’s a category page (is_category(). There isn’t, however, an is_subpage() condition. So, this is the workaround – checking whether the current page is not it’s own parent. In WordPress, pages at the top level of the hierarchy are their own parents. (Let’s not get into genetics, here…I’m concerned.)

Next we’ll use the normal wp_list_pages() function. Again, we’re using the information about the post’s parent in order to determine what pages to list, using the child_of argument to retrieve only the Pages which are children of the current page.

Whoa, there! I see a problem!

Yep, you sure do. At this point, we’re only retrieving child navigation if we’re not on the top level page itself. Well, that’s great. You can get between child pages if you can find your way there!

So that second block of code comes into play. Same idea, except this time we’re fetching children of the current page, without checking whether the page is top level. If it’s got children, great – we’ll display ’em. If it doesn’t – also great – we won’t.

This sytem does also work for multiple hierarchy levels, although it’s a bit awkward. Here’s what you’ll get with three levels in the hierarchy:

  • Top level page, no children: displays category heading, no children. (The category heading could be removed using another conditional statement; I just haven’t done it.)
  • Top level page, with children: displays all descendants (children and grandchildren.) Grandchildren will be in a nested unordered list inside the list item for their parent.
  • Second level page, no children: displays all sibling pages (pages with the same parent).
  • Second level page, with children: displays all sibling pages AND displays the children of the current page, in a nested unordered list inside the list item for that page.
  • Third level page, no children: display all sibling pages at the grandchild level.

And so on. This code functions in WordPress versions 2.01 and above.

28 Comments on “Usable Category Navigation in WordPress Pages”

  1. Yes – what you’re talking about would probably be best handled with tags. What you want to do isn’t truly hierarchical, since everything in the database would have at least one characteristic within each group. This isn’t really something that WordPress natively handles, although you could probably make something work using tags.

  2. So, your method works best for a static Pages hierarchy? Do you know if there would be a way to use this for something where you have multiple ways of drilling down? For example, a bird guide. You might want to click on colors, shapes, sizes, regions first, and then when you are on the “colors” page click on one of the other three and so on?




    You’d quickly get too many different possibilities to set up as a hard categories.

    Thanks, Ian

  3. Hi. I am hoping to use the Pixeled theme for a site. If you look at the nav on the demo, you’ll see it includes fly-overs that show child posts. Would I be able to use your code to do the same thing, but with child pages?


  4. Joe, this works great with WP 2.6.2. Thanks — this was exactly what I was looking for!

  5. The code goes in your theme file sidebar.php or header.php, generally, although it can actually be used in any location in your theme.

    Screenshots would be fairly meaningless, as the code doesn’t define anything about the appearance of the code – just the content of the navigation menu! The appearance will vary enormously depending on your specific usage.

    This code may also be out of date…I’ll check it and make sure it still works with version 2.7, or modify it so that it does.

  6. This post is very interesting, however, I do not understand where to add the code you mentioned? and could you please make some screen shots? It would be easier to understand how the look is going to be… Thanks!

  7. Seker,
    I think Joe has been so clear in his explanation that a .NET developer would certainly have no problems. I only work with HTML (HyperText Markup Language) and CSS (Cascading Style Sheets) and I could understand with this post easily. Maybe if you had a specific problem?
    Thanks Joe!

  8. Hi Joe,

    I am a .NET developer and very new in php and wordpress. Can you please describe your solution a step by step way for dummies. 🙂

  9. It is nice working with software with a supportive community. Definitely one of the big advantages to open source software!

    Hope it works for you!

    I’ve also revised the code above – it may be easier for you to cut and paste (and read) now. 😉

  10. Thanks, Joe! I’ll try it now. What I like most about those of us who work with WordPress is we seem to be forming a community. It means so much to me that you explain your code and try to help figure out what’s happening when some newbie screws it up 🙂 I’ll let you know how it goes!

  11. What you’re describing sounds a lot like a silent PHP (Hypertext PreProcessing) failure, which suggests that there’s an error in the code somewhere: a missing quote, a missing final curly brace (}) or something to that effect.

    It’s hard to be sure, of course, without actually seeing your code…but I suggest giving a close look to the format of the code and searching for any missing pieces. Also, you may want to try changing this section:

    if(get_the_title($post->post_parent) != the_title(’ ‘ , ‘ ‘,false))

    to this:

    if(get_the_title($post->post_parent) != the_title('','',false))

    The only change is in the removal of smart quotes in the_title()’s parameters and the removal of the empty space in the middle, which I’ve found makes a difference in some versions of WP.

  12. Hi Joe,
    I thought you saved my life. Your solution is EXACTLY what I need, only I can’t get it to work. I’m a newbie, so I’m sure it’s me and not your most excellent code. When I put in your code, replace smart quotes with straight ones, still nothing shows up beneath the header. I thought it was my theme so I tried it on a different sidebar and the sidebars disappeared, not everything below the header as it does on mine. Maybe I’m placing it incorrectly?

  13. All right, Jason – If you were using WP Pages, then I think that this scripting would be, in fact, what you’re looking for. What you’re describing seems to be almost exactly what I was doing when I figured this out, in fact.

    However, if (as you describe) the categories in your navigational menu are actually blogging categories — posting groups in the blog — then you’ve got a different issue.

    What you’ll probably want to do is use the Recent Posts plugin to generate a list of posts in the sidebar. It has an option called “match_cats” which you can use to generate a list of posts which match the category of the currently active post. I think it’ll also work with the currently active category; although you’ll have to try it, first.

    Good luck!

  14. Hi, Jason – sorry to get back to you so late, but your comment got pegged as spam by Akismet. I don’t have the time to answer your question right this moment, but I promise I’ll be back tomorrow!

  15. Joe, I guess I’m not understanding this. I don’t see where I’m supposed to drop this code exactly. Frankly I’m not sure if this is what I need.

    Please let me explain…

    Basically I’m trying to create a news/blog site using wp as a CMS (Content Management System). I have 8 main blogging categories set up on a horizontal menu beneath the header. I’d also like to include a verticle menu on the left hand side for “Featured Content” (ie. content that could be changing from week to week). I have not decided if I should be using the category, pages or links function for this specific list. It’s a sports site so over the next month my desired featured content menu might go as follows:

    (Sample Home Page Menu)
    -2008 NFL Draft
    -NCAA Final Four
    -NBA PlayOff Hunt

    When the user clicks on “2008 NFL Draft” i would like to send them to the “2008 NFL DRAFT” page. The “FEATURED CONTENT” menu should morph into the “2008 NFL DRAFT” menu and look like so…

    (Sample “2008 NFL DRAFT” Page Menu)
    2008 NFL DRAFT
    -NFL Mock Drafts
    -College Prospects
    -Rumers & Speculation

    Now lets say the user clicks on “College Prospects”…What was the “2008 NFL DRAFT” menu should morph into the “COLLEGE PROSPECTS” menu and look like so…

    (Sample “COLLEGE PROSPECTS” Page Menu)
    -Offensive Linemen
    -Tight Ends
    -Defensive Linemen
    -Defensive Backs

    I don’t mean to get into obnoxious detail. I just want to make sure I’m very clear about what I’m looking for. I would greatly appreciate any help our direction on this problem as it has been killing me for weeks now!

    Thanks Much

  16. Cool. Glad that worked out for you!

  17. Joe,
    You are the man.
    It worked like a charm. You have no idea how long I’ve been looking for this solution. 🙂

    Thanks a lot for all your help.

    Happy coding.


  18. Well, for a flyout-type menu you don’t really need the scripting in this article — all you really need to do is make use of the standard wp_list_pages() function and then write CSS (Cascading Style Sheets) styles to generate the flyout menu.

    The code in this article is really designed for a contextually variable navigation menu, so that the relevant pages only show up when you need them.

    You will need to use something like this:

    <ul class="adxm menu">


    …in order to make use of the styles. But it should be basically plug’n’play, I think.

  19. Hi, Joe
    I did replace the smart quotes with straight quotes.
    If I use this, it works fine.

    I have 8 parent pages and about 10 pages under them.
    I’m trying to create a flyout menu with the following example, but
    not sure how I’m going to do it but I’m going to give it my best shot.

    Any suggestions?

    Thank you

  20. Well, Issy – I don’t know! It’s working fine for me on WP 2.3.1 – perhaps there’s something else wrong. If you just cut and paste from this site, for example, you’ll end up with a bunch of smart quotes which need to be replaced with straight quotes. (I’d expect that to throw an error rather than throw NOTHING, but it’s worth a shot!)

    Alternately, if your pages aren’t organized in parent/child relationships, this will only produce any results at all on your home page and on post pages.

  21. Hmmm. I haven’t run into any problems with it, myself — let me do some checking, though.

  22. Joe,
    I’m on WP 2.3.1 and I tried your code but all I get is blank results, nothing.
    Is the code different for WP 2.3.1?


  23. Joe- I just discovered your article. I am not a WordPress expert and find the documentation difficult to use. I have been stuck for quite a while, trying to figure out how to list sibling pages. Your code and article completely solved my problem. Thank you for sharing your solution and also writing a clear explanation of what your code does. Bravo!

  24. Thanks, Mike! I have, in fact, read that article…didn’t comment on it at the time, but did find it interesting reading. Thanks for reading!

  25. Great article. Next time I set up WordPress (WP) as a site I will be re-reading this. You might be interested in this article, Joe. It tells you how to order those WP pages to make the short menu a little better.

  26. Thanks! Glad that my timing works well for you, as well! I’ll tell you a secret, though – although I am very careful about marking up abbreviations and acronyms as a matter of routine, I’m actually using a WordPress plugin which does it automatically. Ubernyms is the most recent version of it (not what I’ve got at the moment, but I’ll probably switch in due course.)

  27. Excellent post. Very useful. I’m actually using WordPress as a CMS (Content Management System) for a client. This will come in handy.

    and by the way, you’re like the second person i’ve ever seen religiously use the acronym tag. that’s a good thing.