15

is that possible, without javascript hacks? like this:

<ul class="my_menu">
  <li class="first"> ... </li>
  <li> ... </li>
  <li> ... </li>
  <li class"with_sub"> ... 
    <ul class="my_menu_sub">
      <li class="first"> ... </li>
      <li> ... </li>
      <li> ... </li>
      <li class="last"> ... </li>
    </ul>
  </li>
  <li> ... </li>
  <li> ... </li>
  <li class="last"> ... </li>
</ul>
1
  • 1
    what's wrong with javascript use? is it core functionality they are for? Or could it be seen as a progressive enhancement?
    – Mild Fuzz
    Commented Sep 13, 2010 at 18:29

9 Answers 9

14

A better and simpler approach:

function add_first_and_last($items) {
  $items[1]->classes[] = 'first-menu-item';
  $items[count($items)]->classes[] = 'last-menu-item';
  return $items;
}

add_filter('wp_nav_menu_objects', 'add_first_and_last');
3
  • 1
    Nice and simple. I like it! Commented Jan 8, 2012 at 18:17
  • I see this code works for custom nav menus (I saw it work in a custom menu widget), but it's not working in the same menu being used in the primary nav bar (tested in Twenty Eleven). Should it be? or would that be different code? or just a different filter? Thanks! Commented Feb 12, 2012 at 6:02
  • 2
    This will work for single level menu, but might not for more complex one because at this point it's array with all items, not top level ones.
    – Rarst
    Commented Aug 26, 2012 at 18:08
6

Here is rough snippet that takes care of modifying menu output and adding first/last to first and last class (outer ul is not applied at this stage so doesn't count). Note - requires PHP5 for strripos()

add_filter( 'wp_nav_menu_items', 'first_last_class' );

function first_last_class( $items ) {

    $first = strpos( $items, 'class=' );

    if( false !== $first )
         $items = substr_replace( $items, 'first ', $first+7, 0 );

    $last = strripos( $items, 'class=');

    if( false !== $last )
         $items = substr_replace( $items, 'last ', $last+7, 0 );

    return $items;
}

I am bit stuck with how to make it handle nested lists, but it should get you started at least.

6

Here is a function for only adding the first/last classes to the parent menu items. For most CSS styling, this is all that is necessary.

function nav_menu_add_classes( $items, $args ) {
    //Add first item class
    $items[1]->classes[] = 'menu-item-first';

    //Add last item class
    $i = count($items);
    while($items[$i]->menu_item_parent != 0 && $i > 0) {
        $i--;
    }
    $items[$i]->classes[] = 'menu-item-last';

    return $items;
}
add_filter( 'wp_nav_menu_objects', 'nav_menu_add_classes', 10, 2 );
3
  • 1
    exactly what i was looking for. thanks. it's kind of surprising wp_nav_menu doesn't do this automatically
    – yitwail
    Commented Nov 1, 2012 at 2:43
  • I agree. I believe there was a feature request in the Wordpress bugtracker for a while, but nothing came of it. At least the proper filters exist so we can add these classes ;).
    – Chaoix
    Commented Nov 7, 2012 at 19:10
  • 1
    Thanks. Was looking for the counter, which could be used in the menu walker. your answer allows this. Just added $item->number = $i; and got it in the walker. Thanks!!!
    – BasTaller
    Commented Mar 4, 2014 at 19:54
5

Learn more about the new Menus API in wordpress 3. You can give any element it's own class manually. Also, once mastered, it makes menus a thing of joy to edit.

1
  • 2
    This would be the way to go, simply bring up the nav menu screen in the admin and add a class to the first and last items. Of course if the user moves these menu items, the classes will have to be re-assigned, but this i feel is the most valid/best answer (because there's no coding involved).
    – t31os
    Commented Nov 9, 2010 at 17:20
5

If you have nested menus

function add_first_and_last($items) {
    // first class on parent most level
    $items[1]->classes[] = 'first';
    // separate parents and children
    $parents = $children = array();
    foreach($items as $k => $item){
        if($item->menu_item_parent == '0'){
            $parents[] = $k;
        } else {
            $children[$item->menu_item_parent] = $k;
        }
    }
    // last class on parent most level
    $last = end(array_keys($parents));
    foreach ($parents as $k => $parent) {
        if ($k == $last) {
            $items[$parent]->classes[] = 'last';
        }
    }
    // last class on children levels
    foreach($children as $child){
        $items[$child]->classes[] = 'last';
    }
    // first class on children levels
    $r_items = array_reverse($items, true);
    foreach($r_items as $k => $item){
        if($item->menu_item_parent !== '0'){
            $children[$item->menu_item_parent] = $k;
        }
    }
    foreach($children as $child){
        $items[$child]->classes[] = 'first';
    }
    return $items;
}
add_filter('wp_nav_menu_objects', 'add_first_and_last');

I like the simplicity of Ismaelj's answer, but there needs to be more if you want sub-menu classes.

2

If you don't need support for IE8 or lower, don't forget that you can also use pure CSS:

.my_menu > :first-child,
.my_menu > :last-child {
    /* some styles */
}

jQuery browser support is even better, but it sounds like you're trying to avoid that.

1
  • I'm using that, because last-child is now supported by IE 9 too, and I don't care anymore so much about ie 8, 7..
    – Alex
    Commented Sep 24, 2012 at 10:27
2

Here is some better code for adding first and last menu item classes that includes support for nested submenus.

add_filter( 'wp_nav_menu_objects', 'tgm_filter_menu_class', 10, 2 );
/**
 * Filters the first and last nav menu objects in your menus
 * to add custom classes.
 *
 * This also supports nested menus.
 *
 * @since 1.0.0
 *
 * @param array $objects An array of nav menu objects
 * @param object $args Nav menu object args
 * @return object $objects Amended array of nav menu objects with new class
 */
function tgm_filter_menu_class( $objects, $args ) {

    // Add first/last classes to nested menu items
    $ids        = array();
    $parent_ids = array();
    $top_ids    = array();
    foreach ( $objects as $i => $object ) {
        // If there is no menu item parent, store the ID and skip over the object
        if ( 0 == $object->menu_item_parent ) {
            $top_ids[$i] = $object;
            continue;
        }

        // Add first item class to nested menus
        if ( ! in_array( $object->menu_item_parent, $ids ) ) {
            $objects[$i]->classes[] = 'first-menu-item';
            $ids[]          = $object->menu_item_parent;
        }

        // If we have just added the first menu item class, skip over adding the ID
        if ( in_array( 'first-menu-item', $object->classes ) )
            continue;

        // Store the menu parent IDs in an array
        $parent_ids[$i] = $object->menu_item_parent;
    }

    // Remove any duplicate values and pull out the last menu item
    $sanitized_parent_ids = array_unique( array_reverse( $parent_ids, true ) );

    // Loop through the IDs and add the last menu item class to the appropriate objects
    foreach ( $sanitized_parent_ids as $i => $id )
        $objects[$i]->classes[] = 'last-menu-item';

    // Finish it off by adding classes to the top level menu items
    $objects[1]->classes[] = 'first-menu-item'; // We can be assured 1 will be the first item in the menu :-)
    $objects[end( array_keys( $top_ids ) )]->classes[] = 'last-menu-item';

    // Return the menu objects
    return $objects;

}

You can find the gist here and the associated tutorial here.

0

How about:

 ul li:last-child{
     // do something with the last li
 }

and maybe some http://selectivizr.com/

2
  • This would style the first <li> in every unordered list on the site, not just in the menu. It's also substantively identical to this answer wordpress.stackexchange.com/a/63128/9844
    – mrwweb
    Commented Sep 24, 2012 at 16:16
  • Sorry, pseudo code.
    – a.m.
    Commented Sep 24, 2012 at 16:55
0

Pure CSS, works for me. This will work with submenus also

ul.nav>li:last-of-type a
1
  • only in IE9 and up, which rules it out for pretty much everything I do :)
    – Milo
    Commented Jun 6, 2013 at 18:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.