How to Select an Element Based on the State of Another Element in the Page With Css

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

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:

  1. whether the state of these elements can be represented using simple selectors,
  2. whether a structural relationship can be expressed between these two elements using combinators to form a single complex selector, and
  3. whether the element that you want to target can be made the subject of the resulting complex selector.

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:

  1. 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.

  2. 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.

  3. 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:

Element states, and structural relationships between elements

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:

  • Is there a CSS parent selector?
  • Is there a "previous sibling" CSS selector?

And, more importantly, why is the answer to both of these questions no? The reason is addressed in the next point:

Subject of a selector

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:

  • The subject of section > div[data-status~=finished] is div[data-status~=finished].
  • The subject of section + section is the second section selector.
  • The subjects of section + section > .blink, section + section > .spin are .blink and .spin respectively.
  • Using :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.

A potential solution

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>

Changing CSS of one element based on another element focus/active state

You will need to use CSS combinators

Put this in your code:

.home-search-input:focus ~ .input-group-append > button {
box-shadow: 0 1px 1px 0 rgba(65,69, 73, 0.3), 0 1px 3px 1px rgba(65,69, 73, 0.15) !important;
background-color: red;
}

This (in words) means When input is focused, change button that is in (> sign) .input group that is next to (~ sign) input

This would be your code:

.home-search-input {  border-right: 0;  background-clip: border-box !important;  background: #e9e9e9;  transition: box-shadow ease-in-out .2s;}
.home-search-input:focus,.home-search-input:active,{ background: #ffffff; box-shadow: 0 1px 1px 0 rgba(65, 69, 73, 0.3), 0 1px 3px 1px rgba(65, 69, 73, 0.15);}
.home-search-input:focus~.input-group-append>button,.home-search-input:active~.input-group-append>button { background: #ffffff; box-shadow: 0 1px 1px 0 rgba(65, 69, 73, 0.3), 0 1px 3px 1px rgba(65, 69, 73, 0.15);}
.home-search-btn { background: #e9e9e9; font-size: 15px !important; padding: 6px 12px !important; /* border-radius: 23.79px 23.79px 23.79px 23.79px; */ color: black; border: 1px solid #ced4da; border-left: none;}
<form>  <input type="text" class="form-control home-search-input" placeholder="Recipient's username">  <div class="input-group-append">    <button class="btn btn-outline-secondary home-search-btn" type="button" id="button-addon2">Button</button>  </div></form>

Is there a css selector which selects an element outside the current element?

Selectors express structural relationships between elements. When you ask for a selector for an element that is "outside" another element, you're looking for a combinator that says "this element appears outside the containing scope of this other element".

There is no such combinator.

You could conceivably select specifically the .outside sibling of .parent, but then you run into another problem that there is no parent selector for matching .parent relative to .child:hover like there is for matching .child:hover relative to .parent (that is, .parent > .child:hover).

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

How to change style-properties of one element when another element is hovered purely in css?

This is purely dependent on your HTML structure.

If your affected element is a child of the trigger element, then you can use the pattern

.[TRIGGER_PARENT_CLASS]:hover .[AFFECTED_CHILD_CLASS]

This pattern can nest as deep as you would like. It is also worth noting that you may want to use the Child combinator to select a direct child element.

If your affected element is a sibling of your trigger element, and the trigger element is directly preceding the affected element you can use pattern:

.[TRIGGER_CLASS]:hover ~ .[AFFECTED_SIBLING_CLASS]

You can learn more about Child and Sibling Selectors in this CSS-Tricks article

How to Change a css property based on a state of another component

There are a few approaches to this.

  1. Old school classname toggling

    Pass a prop down to the child component that reflects the state. On the child, use that prop to conditionally render one or more classes that represent the desired presentation.

  2. Assign styles via style prop

    This is similar to #1, but eliminates a layer of abstraction. Instead of assembling a class list you just assemble the CSS styles you'd like to apply as an object.

    const Component = ({ someState }) => 
    <div style={someState ? { border: "5px solid red" } : { color: "#999" }}>
    Some Text
    </div>
  3. Use a CSS-in-JS library

    The downside of the above approach is that you wind up duplicating styles for each instance of your element on the page. CSS-in-JS libraries solve this by extracting your styles into an automatically generated class and applying the class to your component instead. I prefer Emotion, but there are others.

    Using Emotion you're able to accept a className prop from the parent that override the defaults set by the child. This inversion-of-control is really powerful and solves many of the shortcomings with early CSS-in-JS approaches.

    const ParentComponent = () => {
    const [someState] = useState(false)
    return <ChildComponent css={{ color: someState ? "blue" : "red" }} />
    }

    const ChildComponent = ({ className }) =>
    <div
    css={{
    color: "#000",
    border: "4px solid currentColor"
    }}
    className={className}
    >
    Some Text
    </div>

    In the above example, className is assigned by Emotion using the generated class name assigned based on the css prop passed to ChildComponent inside of ParentComponent. The result of this would be a div with a blue border and blue text when someState is false (default). When someState is switched to true, the border and text will be red. This is because the styles passed in via className will override the styles assigned directly via css in Emotion.

Changing the properties of another element using pseudo-classes?

So what would I search to find more information on this subject, what is this principle called?

What is the basic usage for this syntax?

There is actually nothing special about the given selector as a whole. It does however make use of a number of individual concepts to build a complex selector that does something pretty nifty.

The + symbol that you see is called a "combinator"; a combinator expresses a relationship between two elements (that each have their own selectors). The + combinator for example expresses an adjacent sibling relationship between the input and the div:

<!-- Doesn't matter what the parent element is, as long as there is one -->
<div>
<input type="checkbox" checked> <!-- input:checked -->
<div></div> <!-- div -->
</div>

The :checked pseudo-class refers to a form element that is checked, in this case an input element. This pseudo-class is dynamic, in that selecting or deselecting the element will toggle the pseudo-class, but I've included the checked attribute in the markup for the sake of illustration.

Putting them together you have input:checked + div, which selects any div that directly follows a checked input element. It will not select any other div elements, and in particular it will not select those that directly follow unchecked input elements. This technique is collectively known as the checkbox hack — the reason it's a hack is because it often abuses a checkbox control for use cases it was never intended for.

The reason that it targets the div and not the input is because div is the subject of the selector. This is always the rightmost element in a complex selector; any other selectors that are linked to it by combinators are simply there for context. In fact, something like this would be known in CSS1 as a "contextual selector" (although + and :checked didn't exist in CSS1), and this is referenced again in the informative overview of the latest specification.

So, in short, what makes this so clever is the fact that you can attach dynamic pseudo-classes to any part of a selector, and then you can use one or more combinators to link that element to an entirely different one which will end up as the subject of your selector.

Now, the answer to this:

I'm really hoping that there is a way to move up in the DOM tree, and not just sideways and down.

Is that, unfortunately, there isn't a combinator that can do this for you, since combinators only exist for moving down (descendant, child) and sideways (next-sibling, following-sibling). In recent history, a new feature was proposed that would allow you to designate any part of a complex selector as the subject of that selector, eliminating the need for a parent or preceding-sibling combinator:

ul > li  /* Targets the li */
!ul > li /* Targets the ul */

But that has fallen out of favor in a survey held by the working group. See Latest on CSS parent selector for a new proposal called a relational selector — the main reason for which is because it is far more versatile than even the aforementioned subject selector (and of course it also eliminates the need for new combinators).



Related Topics



Leave a reply



Submit