I have elements that can reflect different states, either triggered by the user (:hover
, :focus
, etc.) or manipulated by the server (data-status="finished"
, disabled
, etc.).
I can target the element that has a changed state, but I can't seem to find a way to target other elements in the DOM based on the state of the element in question.
Example:
<section> <div>Element 1</div> <div data-status="finished">Element 2</div> <div>Element 3</div> </section> <section> <div>Element 4</div> <div class="blink">Element 5</div> <div>Element 4</div> <div>Element 4</div> <div class="spin">Element 4</div> ... </section>
or just render the elements with proper styles server-side.
Is there a CSS selector that would let me specify which elements should get selected based on the target element's state?
Something like:
div[data-status~=finished]:affect(.blink, .spin)
that would allow me to also target elements that don't have the same parent with CSS only?
Pseudo-class selectors (select elements based on a certain state)
The [attribute|="value"] selector is used to select elements with the specified attribute, whose value can be exactly the specified value, or the specified value followed by a hyphen (-).
The * selector selects all elements. The * selector can also select all elements inside another element (See "More Examples").
The element+element selector is used to select an element that is directly after another specific element.
The general answer to the canonical question
How do I select an element based on the state of another element in the page with CSS?
is that it depends on exactly three conditions:
While the current Selectors standard has some interesting and sometimes potentially powerful features, the way it is designed makes it extremely limited in area #2 (with #3 being a direct consequence). Some of these limited possibilities are enumerated in other answers, e.g. through the most basic use of child and sibling combinators, clever use of dynamic pseudo-classes (which actually relates to condition #1), or a combination of both.
The problem given in the question, on the other hand, cannot be solved using what is currently available in Selectors for this reason. Most of this boils down to the lack of either a parent selector and/or a previous sibling selector, both of which may seem like trivial features, but have certain implications that make them difficult to define or implement well. In summary:
Yes, the state of these elements can be represented using simple selectors: div
and [data-status~=finished]
for the former, and .blink
and .spin
for the latter two.
The first element can be represented by section > div[data-status~=finished]
, and the two subject elements can be represented by section + section > .blink
and section + section > .spin
respectively. The problem is that it's not possible to write a complex selector incorporating all of these structures, because combinators are one-way, and there is no parent counterpart to the child combinator to join them at the first section
element.
Assuming the answer to the first two questions is also "yes", each of .blink
and .spin
can be made the subject of its own complex selector. (But more on that in the next section.)
If you've been directed to this question, chances are the problem you're trying to solve, like the one given above, cannot be solved with Selectors due to these limitations.
The upcoming standard boasts some new features that will greatly enrich selector syntax and potentially open it (and CSS) up to a host of new possibilities, including a possible solution to the example problem. All of these things will be covered in the following sections, but first I'll explain what each condition means and how it relates to the given example:
The defining characteristic of a selector is that it represents a certain structure of one or more elements in the document tree. This isn't just something I made up — you can actually find this description in the informative overview of the Selectors standard:
A Selector represents a structure. This structure can be used as a condition (e.g. in a CSS rule) that determines which elements a selector matches in the document tree, or as a flat description of the HTML or XML fragment corresponding to that structure.
Selectors may range from simple element names to rich contextual representations.
Each element is represented by a sequence of one or more simple selectors. This sequence is known as a compound selector (I'm using terminology from Selectors 4 here as it is much clearer than what is used in Selectors 3 — see this answer for a non-exhaustive list of terms).
Each simple selector represents a certain state of an element. There are simple selectors for matching the type (or tag name) of an element, a class name, an ID, or an arbitrary attribute. There are also pseudo-classes, which represent abstractions and other special states not directly represented within the document tree, such as the order and position of an element in its hierarchy (:nth-child()
, :nth-of-type()
), user interactions (:hover
, :active
, :focus
, :checked
), the visitedness of a hyperlink (:link
, :visited
), and much more.
In the given example, the div
element with a data-status
attribute whose space-delimited value contains finished
can be represented with a type selector and an attribute selector:
div[data-status~=finished]
If you want the selector to apply only when the pointer is over this element, simply throw in a :hover
pseudo-class:
div[data-status~=finished]:hover
Compound selectors are linked via combinators to form complex selectors. These combinators, the >
, +
and ~
symbols that you may be familiar with, express a relationship between the elements represented by each compound selector. With these two tools alone, you're already able to create some very interesting results as shown in the other answers here. I explain these basics in even further depth in this answer.
In the given example, the following structural relationships can be established:
The first section
element is the parent of div[data-status~=finished]
. This is represented using the child combinator >
:
section > div[data-status~=finished]
The second section
immediately follows the first one as its sibling. This is represented using the adjacent sibling combinator +
:
section + section
Additionally, the second section
is the parent of both .blink
and .spin
. This can be represented using two selectors, one for each child:
section + section > .blink, section + section > .spin
Why are two selectors required? In this case it's mainly because there is currently no syntax for subgrouping two compound selectors into one, so you will have to represent each child element separately. The upcoming Selectors 4 standard introduces a :matches()
pseudo-class that will provide this very subgrouping functionality:
section + section > :matches(.blink, .spin)
Now, since every compound selector in a complex selector represents one element, and thus section + section
represents two elements that are siblings, section > div
represents a parent and a child, and section + section > div
represents a child of a next-sibling, you would think that a parent combinator and a previous-sibling combinator are quite redundant. So why do we commonly get these questions:
And, more importantly, why is the answer to both of these questions no? The reason is addressed in the next point:
The subject of a selector is always represented by the rightmost compound selector. For example, the selector section + section > div
represents three elements, of which div
is the subject. You might say that the div
is selected, or targeted, as in the question, but if you've ever wondered if there was a proper term, it's known as the subject of the selector.
In a CSS rule, styles are applied to the element represented by the subject of the selector. Any child boxes and pseudo-element boxes inherit the styles from this element where appropriate. (The exception is if the subject of the selector includes a pseudo-element, in which case the styles are applied directly to the pseudo-element only.)
Taking the selectors from the previous section, we have the following:
section > div[data-status~=finished]
is div[data-status~=finished]
.section + section
is the second section
selector.section + section > .blink, section + section > .spin
are .blink
and .spin
respectively.:matches()
, the subject of section + section > :matches(.blink, .spin)
is :matches(.blink, .spin)
.It might seem therefore that we do need a parent selector or a previous-sibling selector. But remember that selectors can already represent complex structures. Instead of simply adding new combinators that work opposite of existing ones, it makes sense to seek out a more flexible solution, and that is exactly what the CSSWG has been doing.
Which brings us to the following from the original question:
Is there a CSS selector that would let me specify which elements should get selected based on target element state?
The answer to this is no, and will remain no. However, in the earlier drafts of Selectors 4 (from the FPWD up to the latest working draft from May 2013), there was a proposal for a new feature that would let you pick any of the compound selectors other than the rightmost one, and designate that as the subject of the selector.
However, the subject indicator was recently removed in favor of the :has()
pseudo-class (that was in turn adopted from jQuery). I speculate on a likely reason here:
The reason
:has()
is more versatile is because, with the subject selector, it was never made clear in any draft if a single complex selector could have more than one subject selector (since a single complex selector can only ever have one subject) and/or if functional pseudo-classes such as:matches()
accepted the subject selector. But because a pseudo-class is a simple selector, you know that:has()
can be accepted anywhere a pseudo-class is accepted.
So while you cannot change the subject of a selector, :has()
will completely write off the need to do so, due to its pseudo-class nature. And the best part is that it does this — and then some — all without fundamentally changing selector syntax.
In fact, the example problem can be solved using Selectors 4's :has()
:
/* Combined with the :matches() example from above */ section:has(> div[data-status~=finished]) + section > div:matches(.blink, .spin)
Notice the use of a child combinator: this scopes the relative selector argument to just children of the first section
. Yes, this is the elusive "parent selector" that Web developers the world over have been wanting for years.
And since :has()
comes from jQuery, you can use it today, although :matches()
doesn't exist yet so you'll have to replace that with a call to .filter()
in the meantime:
$('section:has(> div[data-status~=finished]) + section > div') .filter('.blink, .spin') .css('color', 'red');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <section> <div>Element 1</div> <div data-status="finished">Element 2</div> <div>Element 3</div> </section> <section> <div>Element 4</div> <div class="blink">Element 5</div> <div>Element 4</div> <div>Element 4</div> <div class="spin">Element 4</div> ... </section>
It's so versatile, that it will also allow you to not only "target elements that don't have the same parent", but also elements that are completely unrelated, including elements whose positions in the document tree may vary irrespective of one another. This will effectively eliminate condition #2 above, although doing so comes with a major caveat which I'll get to in a second. For example, if we assume that the div
elements in question may appear anywhere with no structural relation to one another, :has()
allows you to do:
:root:has(div[data-status~=finished]) div:matches(.blink, .spin)
... which finds div.blink, div.spin
when div[data-status~=finished]
exists anywhere in the document tree, since any element in the document tree must be a descendant of the document root element.
Now, the caveat that I mentioned, is that using arbitrary complex selectors with :has()
can have serious performance implications, which is why for the longest time parent selectors were never implemented, and both the subject indicator and :has()
have not been implemented yet. The latter two in particular are problematic because the "rightmost compound selector" definition serves as the basis of mainstream CSS selector engines, and these two features seek to challenge it altogether.
This is also why :has()
is tentatively excluded from the fast profile and may therefore not be usable in CSS, as it requires real-time selector matching during page rendering, a situation that is undeniably performance-critical. It will still be accessible through the DOM methods querySelector()
, querySelectorAll()
and matches()
(and any selector libraries that happen to make use of them), however.
That said, the CSSWG has plans to test limited variations of :has()
(e.g. with a single child combinator or sibling combinator) to see if they can be implemented well enough to be included in CSS, which will fulfill the vast majority of use cases, including the first example above.
Unfortunately, CSS selector syntax remains extremely limited today; however, new proposals to the standard are set to bring powerful new possibilities, and a handful of these additions are based on features that selector libraries such as jQuery already offer. Here's hoping that implementations will support these new features for use in CSS.
You are very limited on what you can achieve with the current state of CSS.
In short - you can make CSS elements react to a state change of an element, if they have the same parent AND are siblings, or the parent's children.
The state of an element in CSS is handled by pseudo-classes, which cover most of the typical interactions that a browser handles based on user input.
While this enables you to handle the visual look of the current state of an element and its children in the DOM tree, you still can't make other non-child elements react (with a visual change of style) to the current state of your element, since CSS does not offer a specific type of selector to do that in a flexible way.
You can, however, combine the pseudo-classes with other types of CSS selectors and make this work in certain situations (I will use the hover state since it's the most obvious):
pseudo class + adjacent sibling selector
Adjacent sibling selector matches if element1
and element2
share the same parent in the document tree and element1
immediately precedes element2
. (W3C specification of Adjacent sibling selectors)
div:hover + div { background:red; }
Hover on elements: <div>Element 1</div> <div>Element 2</div> <div>Element 3</div> <div>Element 4</div> <div>Element 5</div>
Pseudo class + General sibling combinator
This combination works the same as the adjacent sibling selector, except that the element being selected doesn't need to immediately succeed the first element; it can appear anywhere after it.
div:hover ~ div { background:red; }
<div>Element 1</div> <section>Element 2</section> <em>Element 3</em> <div>Element 4</div> <div>Element 5</div>
Attribute selector + General sibling combinator / Attribute selector + adjacent sibling selector
States of DOM elements are often stored in data attributes. CSS offers you an attribute selector, which lets you apply style based on the value of an attribute.
The following example sets a lower opacity to all elements following the element with a status of »finished«:
div[data-status~=finished] ~ div { opacity: 0.3; }
<div>Element 1</div> <div data-status="finished">Element 2</div> <div>Element 3</div> <div>Element 4</div> <div>Element 5</div>
:not()
In certain cases, :not()
could help you select all other elements that don't have the state in question active, however, non-simple selectors are not yet supported in CSS3 in :not(), though they are proposed in the new draft Selectors Level 4 draft. So currently, you can not do div:not(.class, .class)
– since only simple selectors are supported ( type selector, universal selector, attribute selector, class selector, ID selector, or pseudo-class).
In the end you can build some complex selectors that may achieve what you want to do, but are usually pretty static, and will probably stop working as soon as the DOM structure changes, but for corner cases you may solve the problem. MDN's pseudo selector list can be handy when combining them.
Unfortunately, at the moment of writing this, you will still have to handle it outside CSS for a solid solution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With