29

My navigation structure looks like this:

<div class="menu">
    <ul>
        <li><a href="#">Page 1</a></li>
        <li><a href="#">Page with Subpages</a>
            <ul class="children">
                <li><a href="#">Page with another subpage</a>
                    <ul class="children">
                        <li><a href="#">subsubpage</a></li>
                    </ul>
                </li>
            </ul>
        </li>
        <li><a href="#">Page 3</a></li>
        <li><a href="#">Page 4</a></li>
    </ul>
</div>

I want all <li>'s that have a subnavigation children to have a little down-arrow applied so users know this page has subpages.

Is it possible to detect in pure css if a <li> has children of ul.children? In my example above, the "Page with Subpages" should have the down arrow applied and the "Page with another subpage" as well.

Right now I'm using jquery to solve this problem:

$(function() {
    $('.menu a').each(function() {
        if ( $(this).parent('li').children('ul').size() > 0 ) {
            $(this).append('<span class="dwn">▼</span>');
        }           
    });
});

Is there an easy pure CSS way to do so?

Thank you.

Thom Smith
  • 13,160
  • 2
  • 40
  • 78
matt
  • 37,699
  • 99
  • 250
  • 390

11 Answers11

92

This is perfectly doable in CSS --

Your structure is this:

 <ul class="menu">
      <li>
           <a href="#">Has arrow</a>
           <ul><li><a href="#"></a></li></ul>
      </li>
      <li>
           <a href="#">Has no arrow</a>
      </li>
  </ul>

Your CSS will be like this --

   //this adds an arrow to every link
  .menu li > a:after { content: '>'; } 

  // this removes the arrow when the link is the only child
  .menu li > a:only-child:after { content: ''; }   

Taking it one step farther, if you want to have a drop down menu where there is a down arrow on the top level items and then right arrows for sub menus, your CSS would look like this

   // set up the right arrows first
   .menu li > a:after { content: '>'; }

   //set up the downward arrow for top level items
   .menu > li > a:after {content: 'v'; }

   //clear the content if a is only child
   .menu li > a:only-child:after {content: ''; }

Here is a jsfiddle of it in action - http://jsfiddle.net/w9xnv/2/

David
  • 961
  • 6
  • 4
7

You could do this:

ul.children:before {
    content:  "▼";
}

here is an example

This doesn't have support in all browsers, here is a list of support

EDIT

Just realised that my example above will be flawed, the arrow will be in the wrong position. Still - if this is an avenue you would like to pursue the possibility is there to align the arrow correctly.

Stuart Burrows
  • 10,804
  • 2
  • 31
  • 33
3

Old question but this is how I would do it:

.menu li > a:not(:only-child):after { content: '▼'; }

You can also make it look nicer like this:

.menu li > a:not(:only-child):after { 
    content: '\00a0\00a0▼'; /* add a little spacing so it's not right next to the text */
    font-size: 0.8rem; /* adjust font size so it looks better */
    vertical-align: middle; /* vertical align to middle */
}
rotaercz
  • 3,174
  • 7
  • 45
  • 80
  • 1
    This worked great for me. I floated the triangle right though: `.menu li > a:not(:only-child):after { content: '▼'; float: right; }` – TecBrat Mar 15 '17 at 14:19
  • 1
    Well, it almost worked. Just need to replace the arrow because Firefox has a problem with it. Might be character encoding on the page where I'm using it. – TecBrat Mar 15 '17 at 14:32
1

In the interests of keeping it simple, here is the solution I like to use:

  1. Place tags around the name of the item acting as a drop-down menu.

    <div class="menu">
        <ul>
            <li><a href="#"><span>Page with Subpages</span></a>
                <ul class="children">
                    <li><a href="#"><span>Page with another subpage</span></a>
                        <ul class="children">
                            <li><a href="#">subsubpage</a></li>
                        </ul>
                    </li>
                </ul>
            </li>
        </ul>
    </div>
    
  2. Add the arrow using CSS:

    #menu span:after /* DropDown Arrow */
    {
       border: 0.313em solid transparent; /* 5 pixels wide */
       border-bottom: none;  /* helps the drop-down arrow stay in the vertical centre of the parent */
       border-top-color: #cfcfcf;   /* colour of the drop-down arrow */
       content: ''; /* content of the arrow, you want to keep this empty */
       vertical-align: middle;
       display: inline-block;
       position: relative;
       right: -0.313em; /* 5 pixels right of the menu text*/
    }
    

Hope this works for you! I believe it to be the simplest method I've come across!

1

For future readers! I was wondering too and then figured it out on my own

we can't check if element has children, but we can check if it is empty

li:not(:emtpy):after {
    content: "V";
    color: black;
    position: relative;
    left: 120%;
    /* Well those styles are not best but you get the idea */
}
1

I think the best way would be to add a class (or that span you're using) to those lis on the server.

Another solution, but probably not easy and not clean, is to add the arrow to all uls and only make them visible (using css) in ul.children. You can then try to position the arrow in front of or behind the parent li. Whether this will work will depend on the design.

GolezTrol
  • 109,399
  • 12
  • 170
  • 196
0

For vertical menu : this js, Works on all = nav.vertical li

$("nav.vertical li:has(ul)").find("a:first").append('<img src="" alt="" class="">');

<nav>
   <ul>
     <li><a href="">Page</a>
         <ul class="children">
           <li><a href="">Post</a></li>
         </ul>
     </li>
   </ul>
</nav>
0

For example

$(function() {
$('.menu a').each(function() {
    if ( $(this).parent('li').children('ul').size() > 0 ) {
        $(this).append('<span></span>');
    }           
  });
});

and the CSS

.menu li a span {
display: -moz-inline-stack;
display: inline-block;  
zoom: 1;
*display: inline;
vertical-align: middle;
background: url(arrow.png) 0 0 no-repeat;
width: 5px;
height: 3px;
margin-left: 5px;
Mayeenul Islam
  • 3,857
  • 5
  • 39
  • 89
0

This is just a refinement of rotaerczs answer. With help from CSS Tricks

#cssmenu li > a:not(:only-child)::after {
    content: "";
    width: 0;
    height: 0;
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-top: 5px solid #fff;
    float: right;
}
Community
  • 1
  • 1
TecBrat
  • 3,265
  • 2
  • 24
  • 39
0

There is no CSS 'parent selector'.

But here is a shorter jquery code:

$('.children').prev('li').append('<span class="dwn">▼</span>');

Your question is similar to this one:

Complex CSS selector for parent of active child

Community
  • 1
  • 1
ngduc
  • 1,357
  • 10
  • 16
0

For Wordpress Please Use the Following

<?php
/**
* Add arrows to menu items
* @author Bill Erickson
* @link http://www.billerickson.net/code/add-arrows-to-menu-items/
* 
* @param string $item_output, HTML output for the menu item
* @param object $item, menu item object
* @param int $depth, depth in menu structure
* @param object $args, arguments passed to wp_nav_menu()
* @return string $item_output
*/
function be_arrows_in_menus( $item_output, $item, $depth, $args ) {
if( in_array( 'menu-item-has-children', $item->classes ) ) {
    $arrow = 0 == $depth ? '<i class="icon-angle-down"></i>' : '<i class="icon-angle-right"></i>';
    $item_output = str_replace( '</a>', $arrow . '</a>', $item_output );
}
return $item_output;
}
add_filter( 'walker_nav_menu_start_el', 'be_arrows_in_menus', 10, 4 );

Thanks to https://www.billerickson.net/code/add-arrows-to-menu-items/

Community
  • 1
  • 1
Zaid Sameer
  • 31
  • 1
  • 6