4080

How do I select the <li> element that is a direct parent of the anchor element?

As an example, my CSS would be something like this:

li < a.active {
    property: value;
}

Obviously there are ways of doing this with JavaScript, but I'm hoping that there is some sort of workaround that exists native to CSS Level 2.

The menu that I am trying to style is being spewed out by a CMS, so I can't move the active element to the <li> element... (unless I theme the menu creation module which I'd rather not do).

Zach Jensz
  • 3,650
  • 5
  • 15
  • 30
jcuenod
  • 55,835
  • 14
  • 65
  • 102
  • 7
    [Safari Tech Preview 137](https://webkit.org/blog/12156/release-notes-for-safari-technology-preview-137/) today introduced the first implementation of the `:has()` selector. – TylerH Dec 21 '21 at 06:32
  • it would be great to have something that works in a way coherent with javascript `el.parenteElement.parenteElement...` like `a:active:parent:parent` or even `a::active::parent::parent`, with two semicolons. It would be not only more coherent with existing pseudo classes but even more intuitive to use and easier to chain to go up more than one level in case of need. I really hope for this type of implementation because I really hate to use `:has(something)`: it doesn't really bring with it not logic nor intuitive nor ergonomic usability. The javascript way is better, and by far than :has(). – willy wonka Sep 01 '22 at 14:50
  • @willywonka I agree. One popular tool (not sure if I'm allowed to mention it) uses `:upward(x)`, where `x` is the number of DOM tree levels to go up. That syntax is quite flexible and works well for this functionality. – RockPaperLz- Mask it or Casket Sep 04 '22 at 10:01
  • As of today, most modern browsers with latest versions like: Chrome/Android browser/Chrome for Android 105+, Edge 105+, Safari/Safari iOS 15.4+, and Opera 91+ supports it by default. Only Firefox 103 to 106 doesn't support it by default, you have to enable it. For mobile: (Opera mini, Samsung internet, and Firefox mobile doesn't support it yet) – Abzoozy Sep 21 '22 at 12:16
  • 1
    @Abzoozy Thanks for the update. To improve your helpful comment, can you define *"it"* and also state which Firefox pref enables this functionality until it is officially rolled out? – RockPaperLz- Mask it or Casket Sep 22 '22 at 07:10
  • @RockPaperLz-MaskitorCasket To Enable the :has() selector in Firefox versions 103 - 107: ` 1. Open Firefox browser and type about:config 2. A warning page will appear, press on the button "Accept the Risk and Continue" to proceed. 3. a Search field with a placeholder "Search preference name" will be on top, type "layout.css.has-selector.enabled" without double-quotations. 4."layout.css.has-selector.enabled" will appear as false just toggle it to true by clicking the button on the right. ` You can also refer to: https://support.mozilla.org/en-US/questions/1139994 – Abzoozy Sep 22 '22 at 12:43
  • @Abzoozy Thank you. This selector is going to help so much, especially for projects in which the HTML cannot be changed. – RockPaperLz- Mask it or Casket Sep 23 '22 at 07:14
  • 2
    Now in Chrome 105 https://developer.chrome.com/blog/has-m105/ – northamerican Oct 05 '22 at 16:36
  • CSS means cascading, from top to bottom, not bottom to top. You can address anything without handling parents. Design matter. – shawee Jun 21 '23 at 14:19
  • Check these references :[link](https://www.w3.org/TR/CSS21/selector.html%23id-selectors), [link](https://web.dev/learn/css/selectors/) – Faegheh Mohammadian Aug 16 '23 at 06:22
  • It is pointed out here that searching for some selectors could be very slow. But the browser could simply add a backlink to each node when needed for optimization. Also, you can almost always select the parent by using a selector that starts from some known parent above the direct parent; and `body` is always known. – David Spector Aug 17 '23 at 13:02

33 Answers33

3406

There is currently no way to select the parent of an element in CSS in a way that works across all browsers.

The Selectors Level 4 Working Draft includes a :has() pseudo-class that will provide this capability. It will be similar to the jQuery implementation, but is currently not supported by Firefox.

li:has(> a.active) { /* styles to apply to the li tag */ }

Firefox is the only major browser not currently supporting it by default.

In the meantime, you'll have to resort to JavaScript in Firefox if you need to select a parent element with full cross-browser support.

TylerH
  • 20,799
  • 66
  • 75
  • 101
Dan Herbert
  • 99,428
  • 48
  • 189
  • 219
331

You can use the :has() CSS pseudo-class

But it has limited browser support (Currently not supported by Firefox).

Yukulélé
  • 15,644
  • 10
  • 70
  • 94
  • 2
    As @Rodolfo Jorge Nemer Nogueira pointed out, it is possible with sass (scss) by using the & sign. That then gets compiled to the desired css. It would be nice if the next css version supported & – TamusJRoyce Aug 05 '20 at 11:48
  • 5
    Actually, I think there is [experimental syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/@supports) equivalent to Andrew's statement: `@supports selector(:has(a))` – xdhmoore Oct 23 '20 at 21:38
  • 11
    @TamusJRoyce ... and what does SASS compile the resultant CSS too? It has to use CSS at the end of the day. – run_the_race Jan 19 '22 at 14:54
  • 4
    As of today, most modern browsers with latest versions like: Chrome/Android browser/Chrome for Android 105+, Edge 105+, Safari/Safari iOS 15.4+, and Opera 91+ supports it by default. Only Firefox 103 to 106 doesn't support it by default, you have to enable it. For mobile: (Opera mini, Samsung internet, and Firefox mobile doesn't support it yet) – Abzoozy Sep 21 '22 at 06:27
  • I had success with `@supports not selector(:has(a, b)) { ... }` to be able to check if a non compatible browser is used, i.e. Firefox at the time of writing. Cfr. https://developer.mozilla.org/en-US/docs/Web/CSS/@supports#testing_for_the_support_of_a_selector – Ludovic Kuty Nov 25 '22 at 10:54
181

I don’t think you can select the parent in CSS only.

But as you already seem to have an .active class, it would be easier to move that class to the li (instead of the a). That way you can access both the li and the a via CSS only.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jeroen
  • 91,079
  • 21
  • 114
  • 132
  • 4
    You can't shift the pseudo selector to the list item, as it is not a focusable element. He's using the :active selector, so when the anchor is active he wants the list item to be affected. List items will never be in the active state. As an aside, it's unfortunate that such a selector doesn't exist. Getting a pure CSS menu to be fully keyboard accessible seems to be impossible without it (using sibling selectors you can make submenus created using nested lists to appear, but once the list gains focus it becomes hidden again). If there are any CSS-only solutions to this particular conun –  Aug 26 '11 at 13:46
  • 18
    @Dominic Aquilina Take a look at the question, the OP is using a class, not a pseudo selector. – jeroen Aug 26 '11 at 14:34
  • Check these references :[link](https://www.w3.org/TR/CSS21/selector.html%23id-selectors), [link](https://web.dev/learn/css/selectors/) – Faegheh Mohammadian Aug 16 '23 at 06:23
150

You can use this script:

*! > input[type=text] { background: #000; }

This will select any parent of a text input. But wait, there's still much more. If you want, you can select a specified parent:

.input-wrap! > input[type=text] { background: #000; }

Or select it when it's active:

.input-wrap! > input[type=text]:focus { background: #000; }

Check out this HTML:

<div class="input-wrap">
    <input type="text" class="Name"/>
    <span class="help hide">Your name sir</span>
</div>

You can select that span.help when the input is active and show it:

.input-wrap! .help > input[type=text]:focus { display: block; }

There are many more capabilities; just check out the documentation of the plugin.

BTW, it works in Internet Explorer.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Idered
  • 2,065
  • 1
  • 18
  • 20
  • 5
    suppose using jquery `patent()` would be faster. This need testing, however – Dan Sep 22 '11 at 19:30
  • 2
    @Idered It fails when you have CSS declaration of a Selector Subject with no child selector (`#a!` alone throws an error, `#a! p` works), and so the others will not works either because of `Uncaught TypeError: Cannot call method 'split' of undefined`: see http://jsfiddle.net/HerrSerker/VkVPs/ – yunzen Apr 16 '13 at 15:33
  • 5
    @HerrSerker I think #a! is an invalid selector, what should it select? – Idered Apr 17 '13 at 13:41
  • 2
    @Idered I don't know. The spec doesn't say it is illegal. #a! should select itself. At least their should be no error in the JavaScript – yunzen Apr 17 '13 at 15:31
  • 5
    Per my comment on the accepted answer, it looks like the polyfill may be required even in the near future after all, because the subject indicator may never be implemented by browsers in CSS. – BoltClock May 02 '13 at 15:35
  • How would you then stick an `:after` on that parent? – toddmo May 01 '17 at 19:54
  • 1
    Please add the contents of the script here, otherwise this is useless when GitHub is down. – TylerH Jun 06 '18 at 13:42
  • good approach, but sadly it's [DEPRECATED] – Facundo Colombier Mar 28 '22 at 20:02
  • 1
    Why deprecated? – khaled_webdev Jun 08 '22 at 08:06
  • Could this be used on pseudo selectors [ parent , div and parent::after ], if div is hovered change the parent:after style? @Idered – Ibrahim Hajjaj Aug 25 '22 at 14:30
123

As mentioned by a couple of others, there isn't a way to style an element's parent/s using just CSS but the following works with jQuery:

$("a.active").parents('li').css("property", "value");
Alastair
  • 6,837
  • 4
  • 35
  • 29
zcrar70
  • 3,158
  • 2
  • 24
  • 18
  • 24
    The `<` selector does not exist (verified using jQuery 1.7.1). – Rob W Feb 23 '12 at 17:53
  • 7
    Perhaps that `<`-syntax worked in 2009 but I've updated it (for 2013). – Alastair May 02 '13 at 05:10
  • 6
    Even better, use jQuery's built-in [`:has()` selector](http://api.jquery.com/has-selector/): `$("li:has(a.active)").css("property", "value");`. It reads similarly to CSS 4's proposed `!` selector. See also: [`:parent` selector](http://api.jquery.com/parent-selector/), [`.parents()` method](http://api.jquery.com/parents/), [`.parent()` method](http://api.jquery.com/parent/). – Rory O'Kane May 08 '13 at 22:12
  • 9
    And rather than using `.css("property", "value")` to style the selected elements, you should usually `.addClass("someClass")` and have in your CSS `.someClass { property: value }` ([via](http://stackoverflow.com/a/6279001/578288)). That way, you can notate the style with the full power of CSS and any preprocessors you are using. – Rory O'Kane May 08 '13 at 22:20
88

There is no parent selector; just the way there is no previous sibling selector. One good reason for not having these selectors is because the browser has to traverse through all children of an element to determine whether or not a class should be applied. For example, if you wrote:

body:contains-selector(a.active) { background: red; }

Then the browser will have to wait until it has loaded and parsed everything until the </body> to determine if the page should be red or not.

The article Why we don't have a parent selector explains it in detail.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Salman A
  • 262,204
  • 82
  • 430
  • 521
  • 16
    actually, the selector would make a very fast browser look slow. – Salman A Feb 20 '14 at 04:09
  • 10
    I trust that browser developers would come up with an implementation which is (at least) as fast as the javascript version, which is the one people end up using anyway. – Marc.2377 Jun 09 '18 at 00:15
  • 2
    @Marc.2377 If you try the above example in JS on your website, I'm never going to visit it. On the other side, I'd say that most of the time, you only care about immediate children, so if it was only limited to the immediate children, it'd be a good addition without too much impact. – xryl669 Sep 07 '19 at 12:41
78

The pseudo element :focus-within allows a parent to be selected if a descendent has focus.

An element can be focused if it has a tabindex attribute.

Browser support for focus-within

Tabindex

Example

.parent:focus-within {
  background: hsl(199deg, 65%, 73%);
}

/* demo styles */
body {
  margin: 0;
}
.parent {
  background: hsl(0, 0%, 80%);
  min-height: 100vh;
  display: grid;
  place-content: center;
}
.child {
  background: hsl(0, 0%, 0%);
  color: white;
  padding: 3rem;
  outline: 0;
  cursor: pointer;
  font: 18px/1.25 sans-serif;
  width: 20ch;
}
<div class="parent">
  <div class="child" tabindex="0">
    Click or Focus on me, my parent will change.
  </div>
</div>
Todd
  • 5,314
  • 3
  • 28
  • 45
sol
  • 22,311
  • 6
  • 42
  • 59
  • 3
    This works fine in my case, thanks! Just a not, the example would be more ilustrative if you change the color to the parent, not to the sibling, this is replace `.color:focus-within .change` with `.color:focus-within`. In my case i'm using bootstrap nav-bar that add the class to the children when active and I want to add a class to the parent .nav-bar. I think this is a pretty common scenario where I own the markup but the component css+js is bootstrap (or other) so I cannot change the behavior. Although I can add tabindex and use this css. Thanks! – cancerbero Jun 29 '18 at 15:13
  • 1
    Productive answer! I do want to add a caveat for the copy-pasters among us - __it's generally considered an antipattern to add non-zero tabindexes__ when adding focus to non interactive elements... Just use 0 or it will always tab to that element first regardless of document flow. I updated the answer to have a tabindex of zero. – Todd Jun 01 '23 at 17:29
69

There isn't a way to do this in CSS 2. You could add the class to the li and reference the a:

li.active > a {
    property: value;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Josh
  • 6,256
  • 2
  • 37
  • 56
  • 4
    by making the a element display:block you can style it to fit the whole of the li area. if you can explain what style you are looking for perhaps I could help with a solution. – Josh Jun 18 '09 at 21:53
40

Try to switch a to block display, and then use any style you want. The a element will fill the li element, and you will be able to modify its look as you want. Don't forget to set li padding to 0.

li {
  padding: 0;
  overflow: hidden;
}
a {
  display: block;
  width: 100%;
  color: ..., background: ..., border-radius: ..., etc...
}
a.active {
  color: ..., background: ...
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Raseko
  • 409
  • 4
  • 3
36

The CSS selector “General Sibling Combinator” could maybe used for what you want:

E ~ F {
    property: value;
}

This matches any F element that is preceded by an E element.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
cobaasta
  • 673
  • 5
  • 2
  • `.component-one:visible ~ body{ background-color: red; }` does not work. – e-info128 Jan 30 '22 at 21:54
  • 1
    @F-Natic I am not surprised that does not work. It is not valid CSS (there is no `:visible` pseudo-class in CSS). `body` is also a direct and *only* descendant of `html`. – TylerH Mar 31 '22 at 14:05
28

Not in CSS 2 as far as I'm aware. CSS 3 has more robust selectors but is not consistently implemented across all browsers. Even with the improved selectors, I don't believe it will accomplish exactly what you've specified in your example.

Mark Hurd
  • 13,010
  • 2
  • 27
  • 28
26

This is the most discussed aspect of the Selectors Level 4 specification. With this, a selector will be able to style an element according to its child by using an exclamation mark after the given selector (!).

For example:

body! a:hover{
   background: red;
}

will set a red background-color if the user hovers over any anchor.

But we have to wait for browsers' implementation :(

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Marco Allori
  • 3,198
  • 33
  • 25
26

You might try to use hyperlink as the parent, and then change the inner elements on hover. Like this:

a.active h1 {color:red;}

a.active:hover h1 {color:green;}

a.active h2 {color:blue;}

a.active:hover h1 {color:yellow;}

This way you can change the style in multiple inner tags, based on the rollover of the parent element.

TylerH
  • 20,799
  • 66
  • 75
  • 101
riverstorm
  • 550
  • 5
  • 8
  • 1
    That is correct, but limits the markup code within the `a` tag to certain elements only, if you want to conform to XHTML standards. For instance, you cannot use a `div` within `a`, without getting a warning of violating the schema. – Ivaylo Slavov Jul 24 '12 at 18:22
  • 2
    Totaly right Ivaylo! "a" is a non-block element, so can't use block elements inside it. – riverstorm Dec 12 '12 at 22:34
  • 2
    In HTML5 it is perfectly fine to put block elements inside links. – Matthew James Taylor Apr 06 '14 at 07:47
  • 2
    ... if it was semantically wrong, they wouldn't have allowed it in HTML5 where it wasn't before. – BoltClock Dec 28 '15 at 15:54
  • @BoltClock replying from the distant future admittedly, but I suspect Matthew's comment about HTML5 was more to contrast "HTML" with "XHTML" (where it isn't allowed, according to Ivaylo/riverstorm), not to contrast HTML *5* with prior versions of HTML. – TylerH Mar 31 '22 at 14:08
25

I know the OP was looking for a CSS solution but it is simple to achieve using jQuery. 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:

$("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,888
  • 9
  • 86
  • 116
  • 4
    Or use `$yourSpan.closest("ul")` and you'll get the parent UL of your span element. Nevertheless your answer is completely offtopic imho. We can answer all of the CSS-taged questions with a jQuery solution. – Robin van Baalen Jul 18 '13 at 17:40
22

Here's a hack using pointer-events with hover:

<!doctype html>
<html>
    <head>
        <title></title>
        <style>
/* accessory */
.parent {
    width: 200px;
    height: 200px;
    background: gray;
}
.parent, 
.selector {
    display: flex;
    justify-content: center;
    align-items: center;
}
.selector {
    cursor: pointer;
    background: silver;
    width: 50%;
    height: 50%;
}
        </style>
        <style>
/* pertinent */
.parent {
    background: gray;
    pointer-events: none;
}
.parent:hover {
    background: fuchsia;
}
.parent 
.selector {
    pointer-events: auto;
}
        </style>
    </head>
    <body>
        <div class="parent">
            <div class="selector"></div>
        </div>
    </body>
</html>
Jan Kyu Peblik
  • 1,435
  • 14
  • 20
21

Updated 2022 CSS Selectors 4

In the CSS Selectors 4 specification, CSS introduces a new selector called :has(), which finally lets us select parents. That means is we’ll be able to target a CSS element that has specific children within it. This is already supported in Safari and is also in Chrome 105. The full support table is shown here.

Parent Selectors workings

In CSS, if we want to select something, we use selectors that descend the DOM.

For example, selecting a p tag within a div tag looks like this:

 div p {
   color: red;
}

Until now, couldn’t really select the div tags which had p tags within them, though, and this meant we had to resort to Javascript. The main reason this wasn’t implemented in CSS is that it’s quite an expensive operation to do. CSS is relatively fast to parse, but selecting parent tags requires a relatively significantly larger amount of processing.

Using the :has selector, we can now select div elements which have a p children, or any normal combination of selectors.

For example, selecting a div with a child p now looks like this:

div:has(p) {
  color: red;
}

This will make any div with a child p red.

Combining parent selection with other selectors

Just like any other CSS selector, we can combine this for specific circumstances. For example, if you want to select only div tags which have direct span children:

div:has(> span) {
  color: red;
}

As the vocabulary of :has suggested, it is not just limited to parent selection.

For example, below we can select a span which :has a sibling div:

span:has(+ div) {
color: red;
}

Or even, selecting an element which does not have a child, by using the :not() selector.

For example, the following will select any div which does not have a p child:

div:not(:has(p)) {
 color: red;
}

Selecting elements that only contain text in CSS

One very common problem in CSS is that the :empty tag does not select elements that contain any text - so sometimes an element can contain one space, and :empty will not apply. The :has selector gives us the power to select elements that only contain text nodes and no other child elements.

Although this is not the perfect solution for simply :empty elements with spaces (as this will select any element with just text and no additional HTML DOM elements) - it does give us the ability to select DOM elements with only text nodes, which was not previously possible. We can achieve this with the following code:

div:not(:has(*)) {
  background: green;
}
pr96
  • 994
  • 5
  • 17
14

Currently there is no parent selector & it is not even being discussed in any of the talks of W3C. You need to understand how CSS is evaluated by the browser to actually understand if we need it or not.

There is a lot of technical explanation here.

Jonathan Snook explains how CSS is evaluated.

Chris Coyier on the talks of Parent selector.

Harry Roberts again on writing efficient CSS selectors.

But Nicole Sullivan has some interesting facts on positive trends.

These people are all top class in the field of front end development.

Nathan Tuggy
  • 2,237
  • 27
  • 30
  • 38
Suraj Naik
  • 509
  • 4
  • 7
  • 12
    The `need` is defined by web developers' requirements, whether to have it in the spec is decided by other factors. – nicodemus13 May 15 '14 at 09:04
14

There's a plugin that extends CSS to include some non-standard features that can really help when designing websites. It's called EQCSS.

One of the things EQCSS adds is a parent selector. It works in all browsers, Internet Explorer 8 and up. Here's the format:

@element 'a.active' {
  $parent {
    background: red;
  }
}

So here we've opened an element query on every element a.active, and for the styles inside that query, things like $parent make sense, because there's a reference point. The browser can find the parent, because it's very similar to parentNode in JavaScript.

Here's a demo of $parent and another $parent demo that works in Internet Explorer 8, as well as a screenshot in case you don't have Internet Explorer 8 around to test with.

EQCSS also includes meta-selectors: $prev for the element before a selected element and $this for only those elements that match an element query, and more.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
innovati
  • 527
  • 1
  • 8
  • 12
13

Just an idea for horizontal menu...

Part of HTML

<div class='list'>
  <div class='item'>
    <a>Link</a>
  </div>
  <div class='parent-background'></div>
  <!-- submenu takes this place -->
</div>

Part of CSS

/* Hide parent backgrounds... */
.parent-background {
  display: none; }

/* ... and show it when hover on children */
.item:hover + .parent-background {
  display: block;
  position: absolute;
  z-index: 10;
  top: 0;
  width: 100%; }

Updated demo and the rest of code

Another example how to use it with text-inputs - select parent fieldset

Community
  • 1
  • 1
Ilya B.
  • 952
  • 7
  • 13
  • 3
    It's not at all clear to me how you mean to generalize this to some/many/most of the parent selector use cases, or even exactly which parts of this CSS are doing what. Can you add a thorough explanation? – Nathan Tuggy Oct 28 '15 at 07:57
  • 2
    I did not try to apply this to real world scenarios, that is why I say "Not for production". But I think It can be applied to 2-level menu only with fixed item width. "which parts of this CSS are doing what" - .test-sibling here is actually background of parent item (the last line of CSS). – Ilya B. Oct 28 '15 at 08:08
  • 2
    Added explanation (css section of jsfiddle, starting from "MAIN PART")... And I was mistaken - there may be any number of sublevels. – Ilya B. Oct 28 '15 at 12:59
11

It's now 2019, and the latest draft of the CSS Nesting Module actually has something like this. Introducing @nest at-rules.

3.2. The Nesting At-Rule: @nest

While direct nesting looks nice, it is somewhat fragile. Some valid nesting selectors, like .foo &, are disallowed, and editing the selector in certain ways can make the rule invalid unexpectedly. As well, some people find the nesting challenging to distinguish visually from the surrounding declarations.

To aid in all these issues, this specification defines the @nest rule, which imposes fewer restrictions on how to validly nest style rules. Its syntax is:

@nest = @nest <selector> { <declaration-list> }

The @nest rule functions identically to a style rule: it starts with a selector, and contains declarations that apply to the elements the selector matches. The only difference is that the selector used in a @nest rule must be nest-containing, which means it contains a nesting selector in it somewhere. A list of selectors is nest-containing if all of its individual complex selectors are nest-containing.

(Copy and pasted from the URL above).

Example of valid selectors under this specification:

.foo {
  color: red;
  @nest & > .bar {
    color: blue;
  }
}
/* Equivalent to:
   .foo { color: red; }
   .foo > .bar { color: blue; }
 */

.foo {
  color: red;
  @nest .parent & {
    color: blue;
  }
}
/* Equivalent to:
   .foo { color: red; }
   .parent .foo { color: blue; }
 */

.foo {
  color: red;
  @nest :not(&) {
    color: blue;
  }
}
/* Equivalent to:
   .foo { color: red; }
   :not(.foo) { color: blue; }
 */
Community
  • 1
  • 1
roberrrt-s
  • 7,914
  • 2
  • 46
  • 57
11

The CSS parent selector (also know as the :has() selector) has finally landed in Safari TP 137. The feature is currently being implementated in Chrome as well. (MDN Documentation)

Parent selection is done via the pseudo-class :has(). For example, div:has(> .child) will select all <div> elements with a child having a child class.

Other examples:

Selecting direct parent of an element

<div>
  <p>Child Element</p>
</div>
div:has(> p)

Selecting all the parents of an element

<div id="grandparent">
      <div id="parent">
            <div id="child"></div>
      <div>
</div>

The following selector will select both grandparent and parent

div:has(.child)

You can also use it for nested selectors and even with other pseudo classes:

div:has(> :nth-child(10))

Other valid CSS operators can be used to customize the query.

Keep an eye on caniuse.com/css-has for browser compatibility.

TylerH
  • 20,799
  • 66
  • 75
  • 101
Metabolic
  • 2,808
  • 3
  • 27
  • 41
  • could you please help convert $('section:has(h1.heading1)').css('padding', 0); into pure JavaScript? – CodeForGood Jan 30 '23 at 14:08
  • 2
    @CodeForGood `document.querySelector('section:has(h1.heading1)').style.padding = 0` but it may not work in all browser yet – Metabolic Jan 30 '23 at 14:50
  • your code throws `Uncaught DOMException: Document.querySelector: 'section:has(h1.heading1)' is not a valid selector` error – CodeForGood Jan 31 '23 at 11:58
10

Technically there is no direct way to do this. However, you can sort that out with either jQuery or JavaScript.

However, you can do something like this as well.

a.active h1 {color: blue;}
a.active p {color: green;}

jQuery

$("a.active").parents('li').css("property", "value");

If you want to achieve this using jQuery here is the reference for the jQuery parent selector.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Prabhakar
  • 6,458
  • 2
  • 40
  • 51
10

Although there is no parent selector in standard CSS at present, I am working on a (personal) project called axe (ie. Augmented CSS Selector Syntax / ACSSSS) which, among its 7 new selectors, includes both:

  1. an immediate parent selector < (which enables the opposite selection to >)
  2. an any ancestor selector ^ (which enables the opposite selection to [SPACE])

axe is presently in a relatively early BETA stage of development.

See a demo here:

.parent {
  float: left;
  width: 180px;
  height: 180px;
  margin-right: 12px;
  background-color: rgb(191, 191, 191);
}

.child {
  width: 90px;
  height: 90px;
  margin: 45px;
  padding-top: 12px;
  font-family: sans-serif;
  text-align: center;
  font-size: 12px;
  background-color: rgb(255, 255, 0);
}

.child.using-axe < .parent {
  background-color: rgb(255, 0, 0);
}
<div class="parent">
  <div class="child"></div>
</div>

<div class="parent">
  <div class="child using-axe">Here, the axe parent selector turns the outer square red.</div>
</div>


<script src="https://rouninmedia.github.io/axe/axe.js"></script>

In the example above < is the immediate parent selector, so

  • .child.using-axe < .parent

means:

any immediate parent of .child.using-axe which is .parent

You could alternatively use:

  • .child.using-axe < div

which would mean:

any immediate parent of .child.using-axe which is a div

Rounin
  • 27,134
  • 9
  • 83
  • 108
  • 3
    The project is in an early Beta stage at present. You can (at least currently) activate axe selectors in your CSS styles by including `` at the very end of your html document, just before `

    `.

    – Rounin Jul 12 '17 at 16:40
10

The short answer is NO; we don't have a parent selector at this stage in CSS, but if you don't have to swap the elements or classes anyway, the second option is using JavaScript. Something like this:

var activeATag = Array.prototype.slice.call(document.querySelectorAll('a.active'));

activeATag.map(function(x) {
  if(x.parentNode.tagName === 'LI') {
    x.parentNode.style.color = 'red'; // Your property: value;
  }
});

Or a shorter way if you use jQuery in your application:

$('a.active').parents('li').css('color', 'red'); // Your property: value;
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alireza
  • 100,211
  • 27
  • 269
  • 172
10

The W3C excluded such a selector because of the huge performance impact it would have on a browser.

rgb
  • 1,246
  • 1
  • 10
  • 14
  • 30
    false. because the DOM is a tree, they have to go to the parent before getting to the child, so the simply just go back one node. o.o – NullVoxPopuli Nov 10 '11 at 16:56
  • 9
    CSS selectors are a [queue](http://www.snook.ca/archives/html_and_css/css-parent-selectors) so [selector order](http://www.stevesouders.com/blog/2009/06/18/simplifying-css-selectors/) is evaluated rather than the document XPath or DOM hierarchy. – Paul Sweatte Aug 18 '12 at 16:14
  • 4
    @rgb At least that's what they told us. – yunzen Apr 16 '13 at 15:35
4

Any ideas?

CSS4 will be fancy if it adds some hooks into walking backwards. Until then it is possible (though not advisable) to use checkbox and/or radio inputs to break the usual way that things are connected, and through that also allow CSS to operate outside of its normal scope...

/* Hide things that may be latter shown */
.menu__checkbox__selection,
.menu__checkbox__style,
.menu__hidden {
  display: none;
  visibility: hidden;
  opacity: 0;
  filter: alpha(opacity=0); /* Old Microsoft opacity */
}


/* Base style for content and style menu */
.main__content {
  background-color: lightgray;
  color: black;
}

.menu__hidden {
  background-color: black;
  color: lightgray;
  /* Make list look not so _listy_ */
  list-style: none;
  padding-left: 5px;
}

.menu__option {
  box-sizing: content-box;
  display: block;
  position: static;
  z-index: auto;
}

/* &#9660; - \u2630 - Three Bars */
/*
.menu__trigger__selection::before {
  content: '\2630';
  display: inline-block;
}
*/

/* &#9660; - Down Arrow */
.menu__trigger__selection::after {
  content: "\25BC";
  display: inline-block;
  transform: rotate(90deg);
}


/* Customize to look more `select` like if you like */
.menu__trigger__style:hover,
.menu__trigger__style:active {
  cursor: pointer;
  background-color: darkgray;
  color: white;
}


/**
 * Things to do when checkboxes/radios are checked
 */

.menu__checkbox__selection:checked + .menu__trigger__selection::after,
.menu__checkbox__selection[checked] + .menu__trigger__selection::after {
  transform: rotate(0deg);
}

/* This bit is something that you may see elsewhere */
.menu__checkbox__selection:checked ~ .menu__hidden,
.menu__checkbox__selection[checked] ~ .menu__hidden {
  display: block;
  visibility: visible;
  opacity: 1;
  filter: alpha(opacity=100); /* Microsoft!? */
}


/**
 * Hacky CSS only changes based off non-inline checkboxes
 * ... AKA the stuff you cannot unsee after this...
 */
.menu__checkbox__style[id="style-default"]:checked ~ .main__content {
  background-color: lightgray;
  color: black;
}

.menu__checkbox__style[id="style-default"]:checked ~ .main__content .menu__trigger__style[for="style-default"] {
  color: darkorange;
}

.menu__checkbox__style[id="style-one"]:checked ~ .main__content {
  background-color: black;
  color: lightgray;
}

.menu__checkbox__style[id="style-one"]:checked ~ .main__content .menu__trigger__style[for="style-one"] {
  color: darkorange;
}

.menu__checkbox__style[id="style-two"]:checked ~ .main__content {
  background-color: darkgreen;
  color: red;
}

.menu__checkbox__style[id="style-two"]:checked ~ .main__content .menu__trigger__style[for="style-two"] {
  color: darkorange;
}
<!--
  This bit works, but will one day cause troubles,
  but truth is you can stick checkbox/radio inputs
  just about anywhere and then call them by id with
  a `for` label. Keep scrolling to see what I mean
-->
<input type="radio"
       name="colorize"
       class="menu__checkbox__style"
       id="style-default">
<input type="radio"
       name="colorize"
       class="menu__checkbox__style"
       id="style-one">
<input type="radio"
       name="colorize"
       class="menu__checkbox__style"
       id="style-two">


<div class="main__content">

  <p class="paragraph__split">
    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
    tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
    quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
  </p>

  <input type="checkbox"
         class="menu__checkbox__selection"
         id="trigger-style-menu">
  <label for="trigger-style-menu"
         class="menu__trigger__selection"> Theme</label>

  <ul class="menu__hidden">
    <li class="menu__option">
      <label for="style-default"
             class="menu__trigger__style">Default Style</label>
    </li>

    <li class="menu__option">
      <label for="style-one"
             class="menu__trigger__style">First Alternative Style</label>
    </li>

    <li class="menu__option">
      <label for="style-two"
             class="menu__trigger__style">Second Alternative Style</label>
    </li>
  </ul>

  <p class="paragraph__split">
    consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
    cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
    proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
  </p>

</div>

... pretty gross, but with just CSS and HTML it is possible to touch and re-touch anything but the body and :root from just about anywhere by linking the id and for properties of radio/checkbox inputs and label triggers; likely someone'll show how to re-touch those at some point.

One additional caveat is that only one input of a specific id maybe used, first checkbox/radio wins a toggled state in other words... But multiple labels can all point to the same input, though that would make both the HTML and CSS look even grosser.


... I'm hoping that there is some sort of workaround that exists native to CSS Level 2...

I am not sure about the other pseudo classes, but I :checked for pre-CSS 3. If I remember correctly, it was something like [checked] which is why you may find it in the above code, for example,

.menu__checkbox__selection:checked ~ .menu__hidden,
.menu__checkbox__selection[checked] ~ .menu__hidden {
 /* rules: and-stuff; */
}

... but for things like ::after and :hover, I'm not at all certain in which CSS version those first appeared.

That all stated, please don't ever use this in production, not even in anger. As a joke sure, or in other words just because something can be done does not always mean it should.

S0AndS0
  • 860
  • 1
  • 7
  • 20
  • 1
    There has never been a "CSS4" planned, and even CSS3 had been merged into just "CSS" well before 2019. – TylerH Aug 12 '21 at 14:43
  • Tyler CSS4 is being considered by those that maintain the spec. Announcement link → https://www.w3.org/community/css4/ Discussion link → https://github.com/w3c/csswg-drafts/issues/4770 – S0AndS0 Aug 13 '21 at 15:44
  • That's just the name of a discussion group. There will be no technology called CSS4. CSS Modules are versioned independently and have been for many years now. As the actual CSSWG contributors in that random user's GitHub request have already replied [*and wrote a response about*](https://www.smashingmagazine.com/2020/03/css4-pros-cons-discussion/), it's not a good idea. Plus that request has been dead for over a year (you can see the GH w3c account has been deleting recent advocacy comments trying to necro it). – TylerH Aug 13 '21 at 16:04
  • That article is an opinion peace, and overall is a distraction from the spec discussion thread. A handful of off-topic marked comments and one deleted comment does not make intent to _necro_, and I'd expect the Issue to be marked as Closed if the working group where committed to killing CSS4 discussion. It's okay, and a good thing in large projects, that modules are versioned independently; module versions do not conflict with spec versioning. – S0AndS0 Aug 13 '21 at 16:56
  • 1
    It's an opinion piece by a contributing member of the CSSWG extrapolating on her "no" response in the GitHub request, which has languages inactive for over a year. It's safe to say it is not being moved forward with. Module versions *would* conflict massively with spec versioning, as several comments in the thread highlight. Listen, as badly as you want it to be a thing, it's not. It's important to accept that and not confuse readers by mentioning things that don't exist. – TylerH Aug 13 '21 at 18:41
3

No, you cannot select the parent in CSS only.

But as you already seem to have an .active class, it would be easier to move that class to the li (instead of the a). That way you can access both the li and the a via CSS only.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Gaurav Aggarwal
  • 9,809
  • 6
  • 36
  • 74
3

At least up to and including CSS 3 you cannot select like that. But it can be done pretty easily nowadays in JavaScript, you just need to add a bit of vanilla JavaScript, notice that the code is pretty short.

cells = document.querySelectorAll('div');
[].forEach.call(cells, function (el) {
    //console.log(el.nodeName)
    if (el.hasChildNodes() && el.firstChild.nodeName=="A") {
        console.log(el)
    };
});
<div>Peter</div>
<div><a href="#">Jackson link</a></div>
<div>Philip</div>
<div><a href="#">Pullman link</a></div>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Eduard Florinescu
  • 16,747
  • 28
  • 113
  • 179
  • Eduard Florinescu, could you please help convert $('section:has(h1.heading1)').css('padding', 0); into pure JavaScript? – CodeForGood Jan 30 '23 at 14:10
3

Try this...

This solution uses plain CSS2 rules with no Javascript and works in all browsers, old and new. When clicked, the child anchor tag activates its active pseudo-class event. It then simply hides itself, allowing the active event to bubble up to the parent li tag who then restyles himself and reveals his anchor child again with a new style. The child has styled the parent.

Using your example:

<ul>
    <li class="listitem">
        <a class="link" href="#">This is a Link</a>
    </li>
</ul>

Now apply these styles with the active pseudo-class on a to restyle the parent li tag when the link is clicked:

a.link {
    display: inline-block;
    color: white;
    background-color: green;
    text-decoration: none;
    padding: 5px;
}

li.listitem {
    display: inline-block;
    margin: 0;
    padding: 0;
    background-color: transparent;
}

/* When this 'active' pseudo-class event below fires on click, it hides itself,
triggering the active event again on its parent which applies new styles to itself and its child. */

a.link:active {
    display: none;
}

.listitem:active {
    background-color: blue;
}

.listitem:active a.link {
    display: inline-block;
    background-color: transparent;
}

You should see the link with a green background now change to the list item's blue background on click.

enter image description here

turns to

enter image description here

on click.

Stokely
  • 12,444
  • 2
  • 35
  • 23
2

Changing parent element based on child element can currently only happen when we have an <input> element inside the parent element. When an input gets focus, its corresponding parent element can get affected using CSS.

Following example will help you understand using :focus-within in CSS.

.outer-div {
  width: 400px;
  height: 400px;
  padding: 50px;
  float: left
}

.outer-div:focus-within {
  background: red;
}

.inner-div {
  width: 200px;
  height: 200px;
  float: left;
  background: yellow;
  padding: 50px;
}
<div class="outer-div">
  <div class="inner-div">
    I want to change outer-div(Background color) class based on inner-div. Is it possible?
    <input type="text" placeholder="Name" />
  </div>
</div>
VFDan
  • 831
  • 10
  • 26
Sethuraman
  • 654
  • 6
  • 15
  • `The :focus-within CSS pseudo-class matches an element if the element or any of its descendants are focused.` It [works](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within) with every element, not just `input` – LS_ Sep 06 '21 at 15:36
2

There no css (and therefore in css preprocessors) parent selector due to "The major reasons for the CSS Working Group previously rejecting proposals for parent selectors are related to browser performance and incremental rendering issues."

Dmitry Sokolov
  • 1,303
  • 1
  • 17
  • 29
1

I'd hire some JavaScript code to do that. For example, in React when you iterate over an array, add another class to the parent component, which indicates it contains your children:

<div className={`parent ${hasKiddos ? 'has-kiddos' : ''}`}>
    {kiddos.map(kid => <div key={kid.id} />)}
</div>

And then simply:

.parent {
    color: #000;
}

.parent.has-kiddos {
    color: red;
}
DamianoPantani
  • 1,168
  • 2
  • 13
  • 23
1

This is an answer to a similar (but not I feel exactly the same) question which has been closed in favor of this question so I put an answer to How to change the border color when input is checked here.

As it is not possible to style a parent depending on its child's status using pure CSS, this snippet alters the HTML so that there is an empty span element immediately following the input elements. This span element does nothing until the input is hovered at which point it displays itself and expands to fit the same size as the ancestor which has position set. This is the label itself, not the input. Hence the border (shadow) shows as if it is on the label.

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.OuterField {
  display: flex;
}

.listLabel {
  padding: 8px 5px;
  border: 2px solid black;
  width: 32%;
  margin: 1%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-flow: row;
  position: relative;
}

input[type="radio"]~span {
  display: none;
}

input[type="radio"]:checked~span {
  display: inline-block;
  width: 100%;
  height: 100%;
  border: 2px solid red;
  box-shadow: 0 0 0 3px orange;
  position: absolute;
  top: 0;
  left: 0;
}
<div class="OuterField">
  <label for="List1" class="listLabel">List 1
        <input type="radio" id="List1" name="list" value="List1">
        <span></span>
     </label>
  <label for="List2" class="listLabel">List 2
         <input type="radio" id="List2" name="list" value="List2">
         <span></span>
     </label>
</div>
A Haworth
  • 30,908
  • 4
  • 11
  • 14
  • Thanks a lot (+1) but what if there is only `border` without `box-shadow` . How to center than , tried to change `top: -2px` `left: -2px` `width: 102%` `height: 112%` on `span` tag . It worked but on some screen sizes only and don't overlap over black border in all screen sizes . Do I have to give absolute values or there are work around this too . Again thanks for help really appreciate . – Rana Oct 23 '21 at 11:23
  • Found a work around a that could be using `width: calc(100% + 4px);` `height: calc(100% + 4px);` with `top: -2px` `left: -2px` – Rana Oct 23 '21 at 11:32
  • 1
    Glad you have something that works. Yes, it is hard to get an absolute placed element exacty at the same spot as another element to get something like a thin border exactly aligned - even if the line-up is exact CSS-wise the underlying screen pixels may show (as there can be several screen pixels to one CSS pixel) so your solution is a practical one. – A Haworth Oct 23 '21 at 12:37