Community Documentation

Menu items that are not links

Last updated February 8, 2010. Created by keesje on May 11, 2007.
Edited by joachim, ronald_istos, ugerhard, add1sun. Log in to edit this page.

Problem

The Menu system in Drupal 5 lets admins define an arbitrary number of menu items in a hierarchy, each of which is a link to somewhere in the site (or on another site). That's great, unless you want to have a menu tree that has items in it which are not, in fact, links. For that, the following code lets us designate "menu links" that will not actually link anywhere.

Modules

Some modules make this possible:

- Special menu items
- Menu Firstchild

Theme layer: Drupal 5

Alternatively, this can be accomplished in your theme:

Step 1

We will define the magic value <none> to mean "a path to nowhere", just like <front> means "the home page". The crucial part here is the theme_menu_item_link() function, which we will override. Place the following function in your template.php file.

<?php
function phptemplate_menu_item_link($item, $link_item) {
  if (
$item['path'] == '<none>') {
   
$attributes['title'] = $link['description'];
    return
'<span'. drupal_attributes($attributes) .'>'. $item['title'] .'</span>';
  }
  else {
    return
l($item['title'], $link_item['path'], !empty($item['description']) ? array('title' => $item['description']) : array(), isset($item['query']) ? $item['query'] : NULL);
  }
}
?>

Any menu item whose path is <none> will now become a span rather than a link. Note that because it's not a link you can't click on it, so you can't access sub-menu items unless the no-link menu item has the "Expand" checkbox checked so that its children are always visible. If you need to have access to the sub-menu items for things like dropdown/flyout menus, try using the JavaScript version of this snippet.

This method can also be used as a separator. Simply have a menu item with text "------" or similar, and set its path to <none>. It will then show as text with no link, providing a nice separation of the menu.

Step 2

As a nice extra, we can also add additional help text to the menu item edit page. For that you will need to implement a very small module that implements form_alter. The module itself doesn't need anything more than the following code snippet:

<?php
function example_form_alter($form_id, &$form) {
  if (
'menu_edit_item_form' == $form_id) {
   
$form['path']['#description'] .= ' ' . t('Enter %none to have a menu item that generates no link.', array('%none' => '<none>'));
  }
}
?>

That will modify just the menu item edit form and add additional text to the description (the text that appears below the textfield) describing how to use the <none>.

Look here for a Drupal 6 solution.

Comments

how can i do this on drupal

how can i do this on drupal 6 ? because the path dont accept ""

The problem is the new

The problem is the new function in 6.x called menu_valid_path that validates whether a link actually works. I can't see a way around it as is. For my own site, I implemented this (ugly) fix:

I created a node with alias 'blank' and took note of its nid - 15 and then added this to my template.php:

<?php
function SITE NAME_menu_item_link($link) {
 
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }
 
if (
$link['href'] == 'node/15') {
    return
'<span class="submenunolink">'. $link['title'] .'</span>';
  }

  return
l($link['title'], $link['href'], $link['localized_options']);
}
?>

This effectively gives me a span with class "submenunolink" for any menu item pointing to "blank."

This is a sloppy, ugly fix, and I'd love to hear if someone has a better way of doing it!

Another way to 'fix'

I came up with this way to make certain menu items non-links. It turns the menu item into a link to the current page with an empty anchor at the end. The link at the bottom of the page doesn't look nice, but at least clicking on the menu item will not cause a page load.

With this change, you can put in the link path as , like you would , to have an non-link menu item.

Note: This does change two core include files, so it very likely would have to be reapplied after any official fixes.

Module: menu.inc
Function: menu_link_save
Was:

  $item['_external'] = menu_path_is_external($item['link_path'])  || $item['link_path'] == '<front>';

New:
  $item['_external'] = menu_path_is_external($item['link_path'])  || $item['link_path'] == '<front>' || $item['link_path'] == '<none>';

Module: common.inc
Function: url
Add new code after:

  $base = $options['absolute'] ? $options['base_url'] .'/' : base_path();
  $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
  $path = drupal_urlencode($prefix . $path);

New code:

/********** MY CUSTOM CODE - If url is <none>, generate empty link - mainly for use in menu system ********/
if ($original_path == '<none>')
{
    $base = '#';
    $prefix = '';
    $path = '';
    $options['query']='';
    $options['fragment']='';
}
/********END MY CUSTOM CODE*******/

Another way to 'fix' part 2

Maybe I'm wrong, but we need to do this change too because the function valid_menu_path will block the <none> value.

Module: menu.inc
Function: valid_menu_path
Was:

  if ($path == '<front>' || menu_path_is_external($path)) {
    $item = array('access' => TRUE);
  }

New:

  if ($path == '<front>' || $path == '<none>' || menu_path_is_external($path)) {
    $item = array('access' => TRUE);
  }

Yet another way to create a linkless menu item

After reading this discussion, I experimented a bit and found another way. Instead of faking links using external URL's, I just picked an existing link (can be any link, like /node/1, /admin/build, /any-alias) and I just added /nolink to that path. Then I created a hook_menu_item_link function into my template.php file and let it search for the /nolink portion in the path. If it finds a /nolink portion, the function returns a span, otherwise an a tag, like in many of the above examples.

There is one drawback: these links are found by the XML sitemap module and generate elements in the sitemap that go nowhere.

Paul Kemper
Tauros Marketing ICT
http://www.tauros.eu

Simple D6 approach

This is how it could be done in D6:

add node or node alias as a dummy to identify that this link isn't a link. I personally add a "nolink" alias to systempath "node", since I never use the "node" path.

Add this to your template.php

<?php
function yourtheme_menu_item_link($link) {
  if (
$link['type'] && $link['href'] == 'node') {
    return
'<span class="nolink">'.check_plain($link['title']).'</span>';
  }
}
?>

Replace the 'node' part with the valid systempath you use as a dummy for this.

Now add a menulink with "nolink" as path.

Now style the .nolink class to your needs, there you go!
Please feedback if you have issues with this.

Luck,

Kees

Drupal website, webdevelopment

Not quite right, and here's my variant

I guess the solution above will work, but the function is not complete. You need an else clause that returns the default menu item, otherwise all menu items except the nolink items will be blank:

<?php
function yourtheme_menu_item_link($link) {
  if (
$link['type'] && $link['href'] == 'node') {
    return
'<span class="nolink">'.check_plain($link['title']).'</span>';
  }
  else {
    return
theme_menu_item_link($link);
  }
}
?>

I decided to do it a little bit differently. Here's an example using the garland theme.
I added the following to the theme's template.php file:

<?php
/**                                                                                                                                          
* Create special "nolink" menu items. These are modified versions of
* (includes/menu.inc)   function theme_menu_item_link() and
* (includes/common.inc) function l().
*/
function garland_menu_item_link($link) {
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }

  return
nl($link['title'], $link['href'], $link['localized_options']);
}

function
nl($text, $path, $options = array()) {
 
// Merge in defaults.
 
$options += array(
     
'attributes' => array(),
     
'html' => FALSE,
    );

 
// Append active class.
 
if ($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) {
    if (isset(
$options['attributes']['class'])) {
     
$options['attributes']['class'] .= ' active';
    }
    else {
     
$options['attributes']['class'] = 'active';
    }
  }

 
// Remove all HTML and PHP tags from a tooltip. For best performance, we act only
  // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive).
 
if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) {
   
$options['attributes']['title'] = strip_tags($options['attributes']['title']);
  }

 
$p = check_url(url($path, $options));
  if (
$p == "/nolink") {
    return
'<span class="nolink"'. drupal_attributes($options['attributes']) .'>'. ($options['html'] ? $text : check_plain($text)) .'</span>'
;
  }
  else {
    return
'<a href="'. $p .'"'. drupal_attributes($options['attributes']) .'>'. ($options['html'] ? $text : check_plain($text)) .'</a>';
  }
}
?>

Basically I just modified the end of the nl (l) function so that I can check for the URL alias "/nolink" instead of whatever system path you decided to use. Also, the advantage is that tooltips etc work.

I added the following to the theme's style.css file to make the nolink menu items look like the real links:

span.nolink {
  color: #027AC6;
  text-decoration: none;
}

span.nolink:hover {
  color: #0062A0;
  text-decoration: underline;
}

span.nolink:active {
  color: #5895be;
}

As a nice touch, if you're using the DHTML Menu module, you need to modify it a little so that it can expand and collapse the nolink menu items. Here's a little patch:

--- dhtml_menu.js.nolink        2009-01-12 11:13:30.000000000 +0100
+++ dhtml_menu.js       2009-01-23 02:38:52.000000000 +0100
@@ -60,6 +60,11 @@
       Drupal.dhtmlMenu.toggleMenu($(li));
       return false;
     });
+
+    $(li).find('span:first').click(function(e) {
+      Drupal.dhtmlMenu.toggleMenu($(li));
+      return false;
+    });
   });
}

Enjoy!

Note that you're using a

Note that you're using a subtheme of Zen, the first snippet will break your tabs. The else clause should pull Zen's override (zen_menu_item_link) instead of theme_menu_item_link.

For Zen subthemes

To spell it out...this is what I put in my theme's template.php file (which is a Zen subtheme I had called "wmp"). Notice in the "else" conditional, I spelled out "zen" instead of my theme name.

<?php
function wmp_menu_item_link($link) { // <<< Make sure you alter this line for YOUR theme name (mine was "wmp")
 
if (!$link['page_callback'] && strpos( $link['href'], 'no.link')) {
    return
'<a href="javascript:void(0)" class="nolink">'. $link['title'] .'</a>';
  }
  else {
    return
zen_menu_item_link($link); // <<< Make sure this says "zen_"
 
}
}
?>

So, if I make a menu item path "http://no.link", this function will turn that link into a "javascript:void(0)" WITHOUT screwing up the tab formatting of zen themes.

Does work, but....

This solution does work, but once you implement this, in DHTML menu (another module) you cannot use the option "only one menu open at the time"(back-translated from Dutch) any more. Somehow all menus can be open at the same time.

Web architecture and tools for research
http://www.toolsforresearch.com

All using open source software: Drupal, Limesurvey

It's a bit more complicated than that

I tried to get this to work for ages, and eventually I had to hard-code the function into includes/menu.inc to make it work.
Note that the examples above require you to enable the 'path' module in Drupal 6.

This is what I did:
1. create a dummy page, which had the path 'node/15'
2. created a menu item with the path 'node/15'
3. replaced the 'theme_menu_item_link' function in menu.inc with this:

function theme_menu_item_link($link) {
  if (empty($link['localized_options'])) {
    $link['localized_options'] = array();
  }

  if ($link['type'] && $link['href'] == 'node/15') {
    return '<span class="nolink">'.check_plain($link['title']).'</span>';
  }
  else {
        return l($link['title'], $link['href'], $link['localized_options']);
  }
}

I am using the Garland theme, but code in the garland/template.php file does not seem to get executed.
Does anyone have any idea why?

I've thought of a simpler way

I've thought of a simpler way than creating a fake node.

The menu system lets you create links to external sites, and has no way of checking those.
So add a menu item linking to, say, 'http://label.fake' and test for it in the theming function as above:

  if (!$link['page_callback'] && strpos( $link['href'], 'label.fake')) {

This is the solution that worked for me

This gave me everything I wanted. No clickable link, no hand cursor, no page items jumping around.

node/120 is a node I am using to point all unclickable links to.

Added to template.php

<?php
function yourthemename_menu_item_link($link) {
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }
  if (
$link['type'] && $link['href'] == 'node/120') {
    return
'<a href="javascript:void(0)" class="nolink">'. $link['title'] .'</a>';
  }
  else {
    return
l($link['title'], $link['href'], $link['localized_options']);
  }
}
?>

Added to style.css:

.nolink:hover {
  cursor: default;
}

Simple D6 approach... parents without link

Using kees@qrios code, I did this to avoid parent items (a.k.a. items with children) to have links:

<?php
function yourthemename_menu_item_link($link) {
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }
    if (
$link['type'] && $link['has_children']) {
    return
'<span class="nolink">'.check_plain($link['title']).'</span>';
  }
    return
l($link['title'], $link['href'], $link['localized_options']);
}
?>

So ANY item with children at ANY level will have no link.

Experience is What You Get When You Do Not Get What You Want

Love this approach. It did

Love this approach. It did just what I needed. Thank you!

Not for primary links menu

Nice solution but it doesn't work for Primary links menu.

Primary links menu uses theme_links() (http://api.drupal.org/api/function/theme_links/6) for theming.

Yet another thing

You can simply

<?php
unset($links[$langcode]['href']);
?>
to disable link but leave text for any Drupal-constructed "a href" (IMHO).
See for example my last patch for D6 users - http://drupal.org/node/237696#comment-3798330

Drupal 6 + Nice Menus

This is the only version that works for me with Nice Menus module... (I also had strange problems when I tried the dummy node approach, so I used the fake url one - in this case I use http://fake.link for every item I do not want to be linkable)

<?php
function themename_menu_item_link($link) {
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }

  if (!
$link['page_callback'] && strpos( $link['href'], 'fake.link')) {
    return
'<a href="#" class="nolink">'. $link['title'] .'</a>';
  }
  else {
        return
l($link['title'], $link['href'], $link['localized_options']);
  }
}
?>

Note: class nolink is purely optional, but it's good for adding some useful styling like changing the cursor etc.

Hi, I got confused. Where am

Hi,

I got confused. Where am I going to put this?

There was a terribly simple

There was a terribly simple page about this in the D5 guide.
The D6 guide is somewhat more detailed: http://drupal.org/node/341628

Should I read this in order

Should I read this in order to use the function which tekket wrote? Sorry, but I am a newbie in drupal and it seems difficult

well, bits of it. It's a bit

well, bits of it.
It's a bit hardcore.
this for D5 is what you need, should be ok for 6: http://drupal.org/node/55126

I have read what you 've told

I have read what you 've told me and I have concluded that I should put the function of tekket in template.php. I'm using as a theme this of zen's starterkit. The steps I followed were:

1. I renamed themename_menu_item_link to my_theme_menu_item_link, where my_theme is the renamed name of the zen's starterkit.
2. I replaced 'front' path of my menu with http://fake.link.

Though it doesn't work. Every time I select the menu button it's trying to connect me to http://fake.link. What am I missing?

Never mind...It worked. Thanks!

This version worked for me

This version worked for me (Great work!)

to avoid having the page scroll to the top when clicking on the fake link,
use
javascript:void(0)

instead of the

#
sign
inside of return '

This works & additional improvement

This solution works for nice menus (and other menu's occasions where dummy link needed [?])

A quick additional improvement: insert


onclick="return false"

to the return code for dummy link to become as follows:

<?php
function themename_menu_item_link($link) {
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }

  if (!
$link['page_callback'] && strpos( $link['href'], 'fake.link')) {
    return
'<a href="#" onclick="return false" class="nolink">'. $link['title'] .'</a>';
  }
  else {
        return
l($link['title'], $link['href'], $link['localized_options']);
  }
}
?>

so dummy link (#) not causing web browser to search whole page for link anchor #

Another improvement

Thanks digitallica - there was a couple things I wanted to add/share to this improvement. When using nice menus, "menu-path-fake.link" is created as a class in each parent li. I don't believe having a period in the class is good form. I also wanted to utilize javascript:void(0) instead of # so that the current URL isn't read into the href (removing an incorrect URL may be better for SEO?), so I made the below modifications and added a new function from the nice menus module to my sub-theme's template.php file.

<?php
/**
* Creates empty menu item when <a href="http://menu.nolink" title="http://menu.nolink" rel="nofollow">http://menu.nolink</a> is added to the menu path.
*/
function THEMENAME_menu_item_link($link) {
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }

  if (!
$link['page_callback'] && strpos( $link['href'], 'menu.nolink')) {
    return
'<a href="javascript:void(0)" onclick="return false" class="nolink">'. $link['title'] .'</a>';
  }
  else {
        return
l($link['title'], $link['href'], $link['localized_options']);
  }
}

/**
* Helper function that builds the nested lists of a nice menu.
*
* @param $menu
*   Menu array from which to build the nested lists.
*/
function THEMENAME_nice_menu_build($menu) {
 
$output = '';

  foreach (
$menu as $menu_item) {
   
$mlid = $menu_item['link']['mlid'];
   
// Check to see if it is a visible menu item.
   
if ($menu_item['link']['hidden'] == 0) {
     
// Build class name based on menu path
      // e.g. to give each menu item individual style.
      // Strip funny symbols.
     
$clean_path = str_replace(array('http://', '<', '>', '&', '=', '?', ':', 'menu.'), '', $menu_item['link']['href']);
     
// Convert slashes to dashes.
     
$clean_path = str_replace('/', '-', $clean_path);
     
$path_class = 'menu-path-'. $clean_path;
     
// If it has children build a nice little tree under it.
     
if ((!empty($menu_item['link']['has_children'])) && (!empty($menu_item['below']))) {
       
// Keep passing children into the function 'til we get them all.
       
$children = theme('nice_menu_build', $menu_item['below']);
       
// Set the class to parent only of children are displayed.
       
$parent_class = $children ? 'menuparent ' : '';
       
$output .= '<li id="menu-'. $mlid .'" class="'. $parent_class . $path_class .'">'. theme('menu_item_link', $menu_item['link']);
       
// Build the child UL only if children are displayed for the user.
       
if ($children) {
         
$output .= '<ul>';
         
$output .= $children;
         
$output .= "</ul>\n";
        }
       
$output .= "</li>\n";
      }
      else {
       
$output .= '<li id="menu-'. $mlid .'" class="'. $path_class .'">'. theme('menu_item_link', $menu_item['link']) .'</li>'."\n";
      }
    }
  }
  return
$output;
}
?>

Obviously THEMENAME would be the name of your theme. The second function will remove "http://menu." creating the class "menu-path-nolink".

Works great with Nice Menus

To help other forum wanderers, I'd like to say that the code provided by tekket works great with Nice Menus. Steps for newbies:
1) Copy and paste the code at the very bottom of your theme's template.php file, but remove the <?php and ?> php tags.
2) notice the function name is themename_menu_item_link... You must replace "themename" with the name of your theme. It could look something like garland_menu_item_link($link) after you're finished.
3) Purge the Drupal cache using the button at the bottom of the /admin/settings/performance page.
4) Enter "http://fake.link" as the link destination for menu items that shouldn't be links.

Special thanks to ...

tekket's php snippet combo attack with Bassplaya CSS code

Fantastic solution

This solution worked for me.
It's a workaround, but it sure save lots of time and hassle, therefore it's a winning one!

Modified for Drupal 7

Looks like the function changed for Drupal 7.

function mytheme_menu_link(array $variables) {
  $element = $variables['element'];
  $sub_menu = '';

  if ($element['#below']) {
    $sub_menu = drupal_render($element['#below']);
  }
  if (strpos( $element['#href'], 'fake.link')) {
    $output = '<a href="#" class="nolink">' . $element['#title'] . '</a>';
  } else {
  $output = l($element['#title'], $element['#href'], $element['#localized_options']);
  }
  return '<li' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
}

Is this the best way to do this with D7? I was able to wrap the fake.link anchor in an h3 tag and use the menu as an accordion, now that jquery_ui is built in.

CSS to the rescue

I know it's not the best but in the worst case scenario:
you could use css by tricking the user to feel that certain links aren't links..
.certain-links a,
.certain-links a:hover {
cursor: default;
text-decoration: none;
}

jQuery really comes to the rescue.

My client discovered this 'bug' and demanded a fix, I found this thread and spent 45 minute reading and digesting this issue. Every solution presented seems to ignore the power of Drupals Behaviors.

Obviously my solution works for me, but it could work for you too. It's a matter of your comfort level with Drupal Behaviors and Themes.

1) find the js included in your theme

For me:

sites/themes/all/<my_theme>/scripts.js

Yours will possibly be parallel, with your theme insted of "<my_theme>"

2) Using some Dom inspector, find the tightest css rule you need to isolate your troublesome 'A' fathers:

For me:

#headerblocks ul.nice-menu.nice-menu-down > li > a

3) Add a behavior at the end of the javascript file:

Drupal.behaviors.theme_<my_theme> = function ()
{
  $('#headerblocks ' +
    'ul.nice-menu.nice-menu-down > ' +
    'li > ' +
    'a:not(.<my_theme>-processed)').attr('href', '#')
                                   .addClass('<my_theme>-processed');
};

As is, this snipit won't work, replace <my_theme> with the name of your theme. Also note that the css selector from step 2 is incorporated in the javascript string there, but we add the text [':not(.<my_theme>-process)'] after the 'a'.

The Javascript here is selecting the father 'A's that aren't yet processed and replacing their href with '#' and marking them as processed.

Voila, quick, correct, and clear.

Some possible problem you might have:

  • You're not using a theme with javascript
  • (my suggestion is to change to one, or modify your theme to do so)

  • You can't find the css selector for your Father 'A'
  • (ummm, I can't give any direct advice on this, ask someone about your site)

  • You want to make the cursor style to change blah blah css stuff
  • (Well the last line of code there is ".addClass('someclass');" add your own and use css for styling

  • You want a leaf node with no link, or you want some other combination of links and not links
  • (If you want other hard to define, or specific items, I think you can use secondary rules in your javascript Behavior to specify them, Since these aren't really likely to be needed on every site, consider this a per-site customization, that way you really give the client what they want.)

  • Finally, note that some clients fear Javascript, if this is true of your clients, then they may still have sucky menus 0.o

Have Fun and Good Luck

Jeff

Module to solve your problem

This module does the trick:

http://drupal.org/project/special_menu_items

"There is nothing, absolutely nothing, half so worth doing as simply messing around in boats!"
Grasmash.com - Drupal Tech Blog

taxonomy menu items with no link

thanks a ton madmatter23 :)
finally able to get items on my taxonomy menu with no link.

simplify

bit buggy though

A lot of people are getting this: http://drupal.org/node/507758

another solution

...someone may find it useful... (works for me)
http://smithwebapps.com/javascript-disable-drupal-parent-menu-links

where?

Sorry... but where to insert this code?

Regards,
crístian

Perfect Solution

Hi,

put the below line in path & put your desire title

http://#

Thanks,
Amit Garg
amit@highpixel.in
http://highpixel.in

Perfect Solution

Hi,

put the below line in path & put your desire title

http://#

Thanks,
Amit Garg
amit@highpixel.in
http://highpixel.in

This module does the trick:

This module does the trick: http://drupal.org/project/menu_item_container
It's maintained by the same person who made quite a few other menu modules.

no help

only if you want your submenus always expanded, it seems, which is not what many are looking for....and the Special Menu Items module did not work for me (D7) either.

About this page

Drupal version
Drupal 5.x, Drupal 6.x
Audience
Themers

Theming Guide

Drupal’s online documentation is © 2000-2012 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License.