331

Is there a way to select a parent element based on the class of a child element in the class? The example that is relevant to me relating to HTML output by a nice menu plugin for http://drupal.org. The output renders like this:

<ul class="menu">  
    <li>  
        <a class="active">Active Page</a>  
    </li>  
    <li>    
        <a>Some Other Page</a>  
    </li>  
</ul>  

My question is whether or not it is possible to apply a style to the list item that contains the anchor with the active class on it. Obviously, I'd prefer that the list item be marked as active, but I don't have control of the code that gets produced. I could perform this sort of thing using javascript (JQuery springs to mind), but I was wondering if there is a way to do this using CSS selectors.

Just to be clear, I want to apply a style to the list item, not the anchor.

BoltClock
  • 630,065
  • 150
  • 1,295
  • 1,284
Deeksy
  • 4,786
  • 2
  • 20
  • 25
  • 1
    Exactly the same problem with Telerik ASP.NET Rad Tabstrip Control... – Juan Calero May 11 '10 at 14:21
  • On searching in Google I had the opposite problem and came across this question, trying to select the child of an element and its as easy as : #nav_sub li.active a – Kieran Andrews Oct 18 '10 at 03:27
  • 7
    @Kieran to be nitpicky, `x y` matches `y` if it's a *descendant* of `x` while `x>y` matches `y` if it's a *child* of `x` – MatrixFrog Jan 20 '11 at 03:49
  • 1
    In addition to the very helpful answers given, I found it useful to look at a detailed description of css selectors, in the following address: http://www.w3.org/TR/CSS2/selector.html – green-i Sep 13 '10 at 18:52
  • There is a trick to use :parent in your css. checkout https://stackoverflow.com/a/50657951/1249617 – Ton Jun 03 '18 at 08:13
  • Where other's may tell ya _not yet_ or to _use JavaScript_, I'll tell ya that it's possible **but** in a round about way to _touch_ anything but `body` and `:root` from anywhere else with just CSS and HTML. However, I **cannot** recommend [this](https://stackoverflow.com/a/56504080/2632107) in production. – S0AndS0 Jun 09 '19 at 05:42
  • I wish there were some CSS selectors which allow to select ancestors based on descendants:( – pouya Oct 31 '20 at 21:50

10 Answers10

246

According to Wikipedia:

Selectors are unable to ascend

CSS offers no way to select a parent or ancestor of element that satisfies certain criteria. A more advanced selector scheme (such as XPath) would enable more sophisticated stylesheets. However, the major reasons for the CSS Working Group rejecting proposals for parent selectors are related to browser performance and incremental rendering issues.

And for anyone searching SO in future, this might also be referred to as an ancestor selector.

Update:

The Selectors Level 4 Spec allows you to select which part of the select is the subject:

The subject of the selector can be explicitly identified by prepending a dollar sign ($) to one of the compound selectors in a selector. Although the element structure that the selector represents is the same with or without the dollar sign, indicating the subject in this way can change which compound selector represents the subject in that structure.

Example 1:

For example, the following selector represents a list item LI unique child of an ordered list OL:

OL > LI:only-child

However the following one represents an ordered list OL having a unique child, that child being a LI:

$OL > LI:only-child

The structures represented by these two selectors are the same, but the subjects of the selectors are not.

Although this isn't available (currently, November 2011) in any browser or as a selector in jQuery.

BoltClock
  • 630,065
  • 150
  • 1,295
  • 1,284
Sam Hasler
  • 13,310
  • 9
  • 68
  • 101
  • 18
    The CSS Selectors 4 spec has now included the ability for selectors to ascend. http://stackoverflow.com/q/1014958/392 – Dan Herbert Nov 22 '11 at 17:22
  • 8
    Just FYI, MooTools has supported CSS level 4 selectors for a couple of years now - and ironically, the Slick selector engine in MooTools actually was able to process these selectors before the spec draft was even published --> https://github.com/mootools/slick/wiki/How-do-I-specify-a-different-subject-for-the-selector%3F – csuwldcat Apr 25 '12 at 17:57
  • @csuwldcat: The subject selector has been discussed for several years now, possibly almost a decade from the CSS2.0 halcyon days. But now that sel4 has been updated to use `!` for choosing the subject (a few months following your comment), that implementation is tentatively obsolete/non-conforming/off/etc. I wonder when/if they'll update their implementation accordingly, or wait for it to stabilize. – BoltClock Oct 19 '12 at 14:41
  • 1
    Is CSS Selectors 4 spec is available now? (December 2012) – Sukanta Paul Dec 12 '12 at 15:21
  • 4
    For anyone still looking at this, the level 4 module does say that the `:has()` selector (previously `$`) is only available in JavaScript, and **will not** be available in CSS. This is due to the single-pass nature of the CSS parser. – jhpratt Aug 22 '17 at 03:07
159

Unfortunately, there's no way to do that with CSS.

It's not very difficult with JavaScript though:

// JavaScript code:
document.getElementsByClassName("active")[0].parentNode;

// jQuery code:
$('.active').parent().get(0); // This would be the <a>'s parent <li>.
Dave Ward
  • 57,126
  • 11
  • 114
  • 134
41

Late to the party again but for what it's worth it is possible using jQuery to be a little more succinct. In my case I needed to find the <ul> parent tag for a <span> tag contained in the child <li>. jQuery has the :has selector so it's possible to identify a parent by the children it contains (updated per @Afrowave's comment ref: https://api.jquery.com/has-selector/):

$("ul").has("#someId")

will select the ul element that has a child element with id someId. Or to answer the original question, something like the following should do the trick (untested):

$("li").has(".active")
David Clarke
  • 12,002
  • 8
  • 80
  • 105
  • 2
    [JSFiddle demo](http://jsfiddle.net/zh6wubrq/) for testing... – revoke Oct 08 '14 at 09:38
  • 2
    jQuery recommends changing `:has(selector)` to this: `$("li").has('active')`. I implemented it on @revoke JSFiddle demo above. https://api.jquery.com/has-selector/ – Afrowave Sep 25 '17 at 11:28
29

THE “PARENT” SELECTOR

Right now, there is no option to select the parent of an element in CSS (not even CSS3). But with CSS4, the most important news in the current W3C draft is the support for the parent selector.

$ul li:hover{
    background: #fff;
}

Using the above, when hovering an list element, the whole unordered list will be highlighted by adding a white background to it.

Official documentation: https://www.w3.org/TR/2011/WD-selectors4-20110929/#overview (last row).

Community
  • 1
  • 1
Praveen Kumar Purushothaman
  • 154,660
  • 22
  • 177
  • 226
  • 3
    Per the [current draft](https://www.w3.org/TR/selectors4/), the current syntax is [`!subject > selector`](http://css4-selectors.com/selector/css4/subject-of-selector-with-child-combinator/). I found http://css4-selectors.com/ to be a good reference on this. At the time of this comment, no browsers support this selector. – Rob Hall Jun 16 '17 at 01:36
16

The first draft of Selectors Level 4 outlines a way to explicitly set the subject of a selector. This would allow the OP to style the list element with the selector $li > a.active

From Determining the Subject of a Selector:

For example, the following selector represents a list item LI unique child of an ordered list OL:

OL > LI:only-child

However the following one represents an ordered list OL having a unique child, that child being a LI:

$OL > LI:only-child

The structures represented by these two selectors are the same, but the subjects of the selectors are not.

Edit: Given how "drafty" a draft spec can be, it's best to keep tabs on this by checking the CSSWG's page on selectors level 4.

Community
  • 1
  • 1
Brian
  • 974
  • 7
  • 9
6

Future answer with CSS4 selectors

New CSS Specs contain an experimental :has pseudo selector that might be able to do this thing.

li:has(a:active) {
  /* ... */
}

The browser support on this is basically non-existent at this time, but it is in consideration on the official specs.


Answer in 2012 that was wrong in 2012 and is even more wrong in 2018

While it is true that CSS cannot ASCEND, it is incorrect that you cannot grab the parent element of another element. Let me reiterate:

Using your HTML example code, you are able to grab the li without specifying li

ul * a {
    property:value;
}

In this example, the ul is the parent of some element and that element is the parent of anchor. The downside of using this method is that if there is a ul with any child element that contains an anchor, it inherits the styles specified.

You may also use the child selector as well since you'll have to specify the parent element anyway.

ul>li a {
    property:value;
}

In this example, the anchor must be a descendant of an li that MUST be a child of ul, meaning it must be within the tree following the ul declaration. This is going to be a bit more specific and will only grab a list item that contains an anchor AND is a child of ul.

SO, to answer your question by code.

ul.menu > li a.active {
    property:value;
}

This should grab the ul with the class of menu, and the child list item that contains only an anchor with the class of active.

Matt Wagner
  • 780
  • 6
  • 13
  • 12
    That doesn't answer the question, unfortunately, as the style information will be applied to the anchor tag, not the list item tag. The issue is not how to use selectors, but how to SELECT the list item based on the class of a child. So your answer will still only select anchors with the class active. – Deeksy Jun 13 '12 at 23:34
1

Many people answered with jQuery parent, but just to add on to that I wanted to share a quick snippet of code that I use for adding classes to my navs so I can add styling to li's that only have sub-menus and not li's that don't.

$("li ul").parent().addClass('has-sub');
Arun
  • 108,644
  • 21
  • 263
  • 366
ggedde
  • 466
  • 4
  • 9
1

I actually ran into the same issue as the original poster. There is a simple solution of just using .parent() jQuery selector. My problem was, I was using .parent instead of .parent(). Stupid mistake I know.

Bind the events (in this case since my tabs are in Modal I needed to bind them with .live instead of a basic .click.

$('#testTab1 .tabLink').live('click', function() {
    $('#modal ul.tabs li').removeClass("current"); //Remove any "current" class
    $(this).parent().addClass("current"); //Add "current" class to selected tab
    $('#modal div#testTab1 .tabContent').hide();
    $(this).next('.tabContent').fadeIn();   
    return false;
})
$('#testTab2 .tabLink').live('click', function() {
    $('#modal ul.tabs li').removeClass("current"); //Remove any "current" class
    $(this).parent().addClass("current"); //Add "current" class to selected tab
    $('#modal div#testTab2 .tabContent').hide();
    $(this).next('.tabContent').fadeIn();   
    return false;
})

Here is the HTML..

<div id="tabView1" style="display:none;">
  <!-- start: the code for tabView 1 -->
  <div id="testTab1" style="width:1080px; height:640px; position:relative;">
    <h1 class="Bold_Gray_45px">Modal Header</h1>
    <div class="tabBleed"></div>
    <ul class="tabs">
      <li class="current"> <a href="#" class="tabLink" id="link1">Tab Title Link</a>
        <div class="tabContent" id="tabContent1-1">
          <div class="modalCol">
            <p>Your Tab Content</p>
            <p><a href="#" class="tabShopLink">tabBased Anchor Link</a> </p>
          </div>
          <div class="tabsImg"> </div>
        </div>
      </li>
      <li> <a href="#" class="tabLink" id="link2">Tab Title Link</a>
        <div class="tabContent" id="tabContent1-2">
          <div class="modalCol">
            <p>Your Tab Content</p>
            <p><a href="#" class="tabShopLink">tabBased Anchor Link</a> </p>
          </div>
          <div class="tabsImg"> </div>
        </div>
      </li>
    </ul>
  </div>
</div>

Of course you can repeat that pattern..with more LI's

Igor Ivancha
  • 3,231
  • 4
  • 28
  • 39
John Drefahl
  • 538
  • 3
  • 17
  • By the way.. if the pattern confuses you due to it not being a standard jQuery Tab's pattern its because we had to build this one from scratch for it to be JAWS screen reader accessible. I will be doing a post with that soon. – John Drefahl Oct 29 '11 at 01:23
1

I had the same problem with Drupal. Given the limitations of CSS, the way to get this working is to add the "active" class to the parent elements when the menu HTML is generated. There's a good discussion of this at http://drupal.org/node/219804, the upshot of which is that this functionality has been rolled in to version 6.x-2.x of the nicemenus module. As this is still in development, I've backported the patch to 6.x-1.3 at http://drupal.org/node/465738 so that I can continue to use the production-ready version of the module.

Mark B
  • 4,328
  • 2
  • 22
  • 29
0

Another thought occurred to me just now that could be a pure CSS solution. Display your active class as an absolutely positioned block and set its style to cover up the parent li.

a.active {
   position:absolute;
   display:block;
   width:100%;
   height:100%;
   top:0em;
   left:0em;
   background-color: whatever;
   border: whatever;
}
/* will also need to make sure the parent li is a positioned element so... */
ul.menu li {
    position:relative;
}    

For those of you who want to use javascript without jquery...

Selecting the parent is trivial. You need a getElementsByClass function of some sort, unless you can get your drupal plugin to assign the active item an ID instead of Class. The function I provided I grabbed from some other genius on SO. It works well, just keep in mind when you're debugging that the function will always return an array of nodes, not just a single node.

active_li = getElementsByClass("active","a");
active_li[0].parentNode.style.whatever="whatever";

function getElementsByClass(node,searchClass,tag) {
    var classElements = new Array();
    var els = node.getElementsByTagName(tag); // use "*" for all elements
    var elsLen = els.length;
    var pattern = new RegExp("\\b"+searchClass+"\\b");
    for (i = 0, j = 0; i < elsLen; i++) {
       if ( pattern.test(els[i].className) ) {
       classElements[j] = els[i];
       j++;
   }
}
return classElements;
}
ЯegDwight
  • 23,615
  • 10
  • 43
  • 51
Jared
  • 1
  • 2