A Method for Mega Menus in Drupal

Posted by Art Williams on September 14, 2011

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.

MONTHLY MARKETING INSIGHTS.

Get thought-provoking and actionable insights to improve how your firm makes a connection with your customers.

LEAVE A COMMENT

The content of this field is kept private and will not be shown publicly.

Plain text

  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Submitted by Danny on Wed, 09/14/2011 - 11:35am

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

Submitted by Art Williams on Wed, 09/14/2011 - 11:43am

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

Submitted by Your Name on Wed, 09/14/2011 - 2:29pm

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...

Submitted by Art Williams on Wed, 09/14/2011 - 3:03pm

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.

Submitted by Art Williams on Tue, 09/20/2011 - 8:39am

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

Submitted by tigron on Mon, 10/03/2011 - 1:17pm

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 ?

    Submitted by Art Williams on Tue, 10/04/2011 - 10:46am

    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

    Submitted by tigron on Tue, 10/04/2011 - 3:09pm

    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';
    }

    Submitted by Art Williams on Tue, 10/04/2011 - 3:14pm

    Tigron,

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

    Art

    Submitted by Benuel on Wed, 12/21/2011 - 11:26am

    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!

    Submitted by Art Williams on Wed, 12/21/2011 - 11:53am

    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/

    Submitted by Mark Carver on Tue, 01/17/2012 - 9:27am

    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 :)

    Submitted by Art Williams on Thu, 01/19/2012 - 8:39am

    @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.

    Submitted by Jt on Wed, 05/23/2012 - 2:15pm

    Lis cant go inside a div with no ul.

    Submitted by Jt on Wed, 05/23/2012 - 2:16pm

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

    Submitted by Amy Peveto on Wed, 05/23/2012 - 2:20pm

    Been there, Jt. :)