2

How do we apply styles to child element (C) of a parent element (P) while specifying their relationship using descendant combinator on occurrence of a pseudo-class state?

A small scenario would be that the child element a of parent element .menu-item is required to change color when .menu-item is hovered on. This is represented in the code given below:

.menu {
  list-style-type: none;
}
.menu-item {
  padding: 0.5rem;
}
.menu-item a {
  text-decoration: none;
  color: #222;
}
.menu-item:hover .menu-item a {
  color: #a2b;
}
<ul class="menu">
  <li class="menu-item">
    <a href="#" class="menu-link">Home</a>
  </li>
  <li class="menu-item">
    <a href="#" class="menu-link">About</a>
  </li>        
  <li class="menu-item">
    <a href="#" class="menu-link">Contact</a>
  </li>
</ul>

However, it works when the following code snippet is written without explicitly specifying that a is a child of .menu-item:

.menu-item:hover a {
  color: #a2b;
}

Is there any other way to achieve this in CSS while explicitly specifying the relationship between elements?

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Srishti Gupta
  • 1,155
  • 1
  • 13
  • 30

3 Answers3

3

You say:

However, it works when the following code snippet is written without explicitly specifying that a is a child of .menu-item:

.menu-item:hover a { color: #a2b; }

It is true that that code does not explicitly specify that a is a child of .menu-item. What it is saying is

'here's the color for any a that is a descendant of a hovered menu-item'

In your case that is what you want as the anchor element won't have other nested anchor elements in it so you change the color of just the one.

It is probably a more easily maintained way of expressing the requirement than definitely requiring that the a be a child (rather than just any descendant) of the menu item because if later on someone placed the anchor element in a div for example you would still want the color to change when the menu-item is hovered.

The other code given in the question:

.menu-item:hover .menu-item a {
  color: #a2b;
}

is saying

when a menu-item is hovered look for all its menu-item descendants and color any of their anchor element descendants.

Nested menu-items is not the structure that you have so nothing happens on a menu-item being hovered.

A Haworth
  • 30,908
  • 4
  • 11
  • 14
2

Is there any other way to achieve this in CSS while explicitly specifying the relationship between elements?

You already are, with the use of the descendant combinator.

Combinators express relationships between two elements, each represented by a compound selector. And a compound selector is made up of one or more simple selectors that all represent some aspects of an element's state. Selectors does not require you to separate an element's state, and that same element's relationship with another element. This works both to its elegance, but also to its detriment as it creates many limitations such as not being able to traverse from a descendant to its ancestors, from a later sibling to its previous siblings, and so on. But that's a separate issue.

In the selector .menu-item:hover a, there are two compound selectors: .menu-item:hover, and a. Each of the three simple selectors represents a certain state, but two of them apply to the same element by appearing in a single compound selector. In this case, the :hover pseudo-class indicates that it's representing .menu-item, albeit only when it is in the hover state.

This doesn't mean or imply that a only descends from .menu-item when it's in the hover state. Instead, it means that the a element will only match when it descends from at least one element (in other words: has at least one ancestor) that matches .menu-item:hover. In fact, if you changed .menu-item:hover to :hover.menu-item, it would still match, because the order between classes and pseudo-classes in a compound selector doesn't matter, they're on equal footing. You could alternatively think of that, then, as "explicitly saying that a is a descendant of an element that's in the hover state", and attempting to write something like :hover.menu-item :hover a, which evidently doesn't work either. That might help you understand why the relationship is already explicitly specified through use of the descendant combinator itself.

Therefore the selector .menu-item:hover a (and, by extension, :hover.menu-item a) means

Apply this rule to any a element
that descends from (or: has an ancestor that is) a .menu-item element that's in the :hover state.

And, since a relationship is already explicitly specified when you use the descendant combinator, it follows that the selector .menu-item:hover .menu-item a, with two descendant combinators, means

Apply this rule to any a element
that descends from a .menu-item element
that, itself, descends from another .menu-item element, one that's in the :hover state.

That's why it doesn't work. Additionally, as A Haworth says, since there aren't any nested .menu-items in your markup, it won't match any a elements at all.

For a much more in-depth explanation to how combinators and element states work in Selectors, see my answer to this question:

How do I select an element based on the state of another element in the page with CSS?

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • Your responses are not merely answers but illuminating articles! – Srishti Gupta Apr 27 '21 at 06:15
  • 1
    @Srishti: Thanks! In case you were wondering, someone flagged your original comment and another moderator deleted it because Stack Overflow tries not to keep these types of comments around. But I do appreciate it, and I wasn't particularly happy that it got deleted in the first place. – BoltClock Apr 27 '21 at 06:16
0

I would be using a:hover directly unless you wanted the entire menu-item div to change colour when hovered

a:hover {
  color: #a2b;
}

<ul class="menu">
  <li class="menu-item">
    <a href="#" class="menu-link">Home</a>
  </li>
  <li class="menu-item">
    <a href="#" class="menu-link">About</a>
  </li>        
  <li class="menu-item">
    <a href="#" class="menu-link">Contact</a>
  </li>
</ul>
.menu {
  list-style-type: none;
}
.menu-item {
  padding: 0.5rem;
}
.menu-item {
  text-decoration: none;
  color: #222;
}
a:hover {
  color: #a2b;
}

There reason .menu-item:hover .menu-item a { color: #a2b; } doesnt work is because the hierarchy would be menu > menu-item > a:hover

spacemonki
  • 291
  • 2
  • 13