A Method for Mega Menus in Drupal

September 14, 2011
Mega Menu Drupal Whitehouse.gov

Editor's note: This article is out-of-date. Please check out our post on Drupal Mega Menus Revisited

Mega Menus have become all the rage in navigation user inteface. When you have a large list of items for a dropdown menu a mega menu makes an efficient way to display the list.  An excellent example of a mega menu is on the White House's website.

In Drupal the way the menu items are built makes it difficult to create a mega menu. We need a way to create columns and it's not proper to wrap a few <li>'s inside a <div> like this:

<ul> 
  <div>
    <li></li>
    <li></li>
  </div>
  <div>
    <li></li>
    <li></li>
  </div>
<ul>

My solution is to add two classes to every <li> on the menu.  One class will be the column number as in 'col-3', and the other class will be the row number as in 'row-5'.

  • Add this function to your template.php file.
  • Change the word YOURTHEME in the function name to the name of your themef
  • Change the $desired_columns & $desired_rows variable values to the number of columns and rows you want on your largest dropdown.
  • Then clear the site cache.

/* add col-# & row-# class to list items */
function YOURTHEME_menu_item($link, $has_children, $menu = '', $in_active_trail = FALSE, $extra_class = NULL) {
static $count = 0;
$desired_rows = 11;
$desired_columns = 3;

  $class = ($menu ? 'expanded' : ($has_children ? 'collapsed' : 'leaf'));
  $count++;
  if (strlen(strstr($class,'expanded'))>0) {
    $count = 0;
  }
  elseif (strlen(strstr($extra_class,'first'))>0) {
    $count = 1;
  }
   
  if (!empty($extra_class)) {
    $class .= ' '. $extra_class;
  }
  if ($in_active_trail) {
    $class .= ' active-trail';
  }
  
  if ($count > 0) {
    $column = '1';
    if (($count > $desired_rows ) && ($count <= ($desired_rows*2) ) {
      $column = '2';
    }
    elseif ($count > ($desired_rows*2 ) {
      $column = '3';
    }
    
    $row = ( $count % $desired_columns );
    
    $class .= ' row-'. $row;
    $class .= ' col-'. $column; 
  }
  
  return '<li class="'. $class .'">'. $link . $menu ."</li>\n";
} 

Now you need to set the css.  My menu has a maximum of 3 rows and 11 items in each row.  The first step in the css is to set the width and height of the containing <ul> and the <li> for each 2nd level menu item.  Then set the position of the <li> as 'absolute'.

Next you want to set the left position for each of the column classes and the top position for each of the rows.

My css code ended up looking like this, but yours may have more or fewer classes depending on your desired rows and columns:


#primary-menu ul.menu li ul.menu {
  width: 600px;
  height: 330px;
}

#primary-menu ul.menu li ul.menu li {
  width: 200px;
  height: 30px;
  position: absolute;
}

#primary-menu ul.menu li ul.menu li.col-1 {
  left: 0;
}

#primary-menu ul.menu li ul.menu li.col-2 {
  left: 200px;
}

#primary-menu ul.menu li ul.menu li.col-3 {
  left: 400px;
}

#primary-menu ul.menu li ul.menu li.row-1 {
  top: 0;
}

#primary-menu ul.menu li ul.menu li.row-2 {
  top: 30px;
}

#primary-menu ul.menu li ul.menu li.row-3 {
  top: 60px;
}

#primary-menu ul.menu li ul.menu li.row-4 {
  top: 90px;
}

#primary-menu ul.menu li ul.menu li.row-5 {
  top: 120px;
}

#primary-menu ul.menu li ul.menu li.row-6 {
  top: 150px;
}

#primary-menu ul.menu li ul.menu li.row-7 {
  top: 180px;
}

#primary-menu ul.menu li ul.menu li.row-8 {
  top: 210px;
}

#primary-menu ul.menu li ul.menu li.row-9 {
  top: 240px;
}

#primary-menu ul.menu li ul.menu li.row-10 {
  top: 270px;
}

/* really row 11 but using the remainders 11 becomes 0 */
#primary-menu ul.menu li ul.menu li.row-0 {
  top: 300px;
}

This method is quick and easy way to implement for a basic mega menu. It will require you to do some math to figure out the height, width, top, and left measurement for each class; and the downside is that it's not very flexible. You'll need to adjust the function code and the css for your unique situation.

Leave your comments below if you decided to try this, or have a better way to do it.  Be sure and wrap your code in the code tags <code>your code </code> or it will be stripped out.

Comments

Thanks, this looks great.

Thanks, this looks great. Will this work for Drupal 6 & 7? Based on "#primary-menu", I am guessing you did this for Drupal 6?

Danny, Great point. This is

Danny,

Great point. This is for D6. I built it for a Drupal Commons site so it hasn't been tested for D7 at all.

-Art

There's a module for that:

There's a module for that: http://drupal.org/project/menu_minipanels :-)

Just used it very successfully on a new D7 site. This module allows for way more flexibility - anything you can stick in a panels 'mini-panel' can be in a menu. I have gone crazy and included 'most popular' items etc...

Nice suggestion! It's D6 & D7

Nice suggestion! It's D6 & D7 compatible. Panels is a lot of overhead for just a menu, though. I think I'd reserve this module for a site that I was already using Panels for other things to make the overhead worth it.

There's also this

There's also this module:

http://drupal.org/project/megamenu

Chrys, I looked at that

Chrys,

I looked at that module before going this route. The module required changing the hierarchy of my menus in a way I didn't want, but in a situation where you are willing to make those changes then it seems like a good solution.

Art

I was looking to test this

I was looking to test this out on a site I'm working on and I've got an error in the code:
if ($count > 0) {
$column = '1';
if (($count > $desired_rows ) && ($count <= ($desired_rows*2) ) {
$column = '2';
}
elseif ($count > ($desired_rows*2 ) {
$column = '3';
}
I'm getting a sysntax error unless I add { after $column = '1';. Once I do that then I get an error on the last line :return '

  • '. $link . $menu ."
  • \n";
    }.

    Any suggestions ?

    Tigron, Sounds to me like you

    Tigron,

    Sounds to me like you might be missing some of the code, can you try copying and pasting it again?

    Or send me the entire function you are using through the contact form on this site and I'll take a look.

    Art

    Thanks for taking time to

    Thanks for taking time to reply and take a look. I hope this is what you needed.


    if ($count > 0) {
    $column = '1';
    if (($count > $desired_rows ) && ($count <= ($desired_rows*2) ) {
    $column = '2';
    }
    elseif ($count > ($desired_rows*2 ) {
    $column = '3';
    }

    Tigron, I really need to see

    Tigron,

    I really need to see the entire function so I'm going to email you directly.

    Art

    You are doing a good work. I

    You are doing a good work. I am also experiencing the same error with D7..

    if ($count > 0) {
    $column = '1';
    if (($count > $desired_rows ) && ($count <= ($desired_rows*2) ) {
    $column = '2';
    }
    elseif ($count > ($desired_rows*2 ) {
    $column = '3';
    }

    thanks for your help in advance!

    Benuel, This method has never

    Benuel,

    This method has never been tested with D7. I'm not sure what the issue is. However, since making this post I figured out a way to accomplish this type of menu using css only. No php coding, necessary.

    My new method uses n-th child pseudo selectors to space the items correctly. I'll try to write a followup post explaining how, but if you want to give it a go read here first:
    http://css-tricks.com/how-nth-child-works/

    https://drupal.org/project/men

    http://drupal.org/project/menu_views

    This approach is rather simple. Attach a view to a menu item.

    There are a few modules out there that really attempt to make mega menus simple, but their UI's are completely cluttered. We designed this module to give the admin the power to attach ANY view to ANY menu item. Once the markup is there, then your theme can style it however it wants :)

    @Mark Carver: Great

    @Mark Carver: Great suggestion.

    Four months after writing this article I look back on it as NOT some of my best work. In fact I wouldn't use the methods in this post now.

    As I mentioned in a previous comment, turning a long list into columns can be handled with pure CSS using n-th child pseudo selectors, but for anything more complicated than that I would recommend Menu Views as you mentioned.

    Lis cant go inside a div with

    Lis cant go inside a div with no ul.

    ... but that's ok, because

    ... but that's ok, because you clearly have a UL. Despite my obvious lack of ability to see it...

    Been there, Jt. :)

    Been there, Jt. :)