Jourkey
Jourkey

Reputation: 34996

Is there a "previous sibling" selector?

The plus sign selector (+) is for selecting the next adjacent sibling.

Is there an equivalent for the previous sibling?

Upvotes: 1888

Views: 1181104

Answers (30)

Ravinder Singh
Ravinder Singh

Reputation: 84

Please check this, it's working in reverse order (selecting the

.one:has(+ .two) {
background: red;
}

.one:has(+ .two) .one-child {
background: yellow;
}
<div class="one">I am div 1, a target 
  <p class="one-child"> I am child of one </p>
</div>

<div class="two">I am div 2, a selector </div>

https://codepen.io/sanjuravinder/pen/LEYpRme

Upvotes: 0

Quentin
Quentin

Reputation: 944428

Selectors level 4 proposes :has() which allows you to select a previous sibling with:

previous:has(+ next) {}

or (for a general previous sibling rather than adjacent one):

previous:has(~ next) {}

It has excellent browser support.

ul {
  display: flex;
  list-style: none;
}

li {
  padding: 3px;
  margin: 1em;
}

li:has(+ .x) {
  outline: solid blue 3px;
}

li:has(~ .x) {
  background: yellow;
}

.x {
  font-weight: bold;
}
<h1>Demo</h1>
<ul>
  <li>one
  <li>two
  <li>three
  <li class="x">four
  <li>five
</ul>

Upvotes: 529

Christina
Christina

Reputation: 34642

Using the :has() selector can select the previous sibling. Here's a codepen: https://codepen.io/carasmo/pen/gOVvbaW/da0209a93d7fcedf8965e021e0ffc772.

Also see: https://css-tricks.com/the-css-has-selector/

<div class="wrapper">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

.wrapper {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 80%;
  padding: 3vw;
}

.wrapper > div {
  width: 50px;
  height: 50px;
  background: red;
}
        
.wrapper > div:nth-child(4) {
  border-radius: 50%;
  width: 80px;
  height: 80px;
  background: blue;
}

.wrapper div:has(+ :nth-child(4)) {
  width: 80px;
  height: 80px;
}

Upvotes: 0

Thiago Telles
Thiago Telles

Reputation: 31

I found a way, try this:

.prev_item:has(+ .next_item) {
  color: red;
}

It will change the color of .prev_item if it is the sibling before .next_item;

Upvotes: 2

Roman Pushkin
Roman Pushkin

Reputation: 6099

Things have changed in 2023. Now there is such selector:

    // 1 item before
    :has(+ .item:hover) {
        ...
    }

If you want to select 3 items before, you do something like:

    // 3 items before
    :has(+ .item:hover),
    :has(+ .item + .item:hover),
    :has(+ .item + .item + .item:hover) {
        ...
    }

See my demo here https://codepen.io/ro31337/pen/YzJbEZv

Upvotes: 29

ab_Dominoble_dev
ab_Dominoble_dev

Reputation: 111

Here's my working solution using only CSS for gold stars (SCSS specifically in this case)

  • I have 5 initially grey stars inside a container div
  • On hover of the container div, I make all the stars gold
  • On hover over the specific star, I make all successor sibling divs grey

Below is svelte code (but can easily be converted into HTML)

<button id="review">
        <div id="star-container">
          <div><Fa icon="{faStar}" /></div>
          <div><Fa icon="{faStar}" /></div>
          <div><Fa icon="{faStar}" /></div>
          <div><Fa icon="{faStar}" /></div>
          <div><Fa icon="{faStar}" /></div>
        </div>
</button>

<style lang="scss">
  #review {
    color: $cool-gray;
    #star-container {
      display: flex;
      &:hover {
        color: $custom-gold;
      }
      div {
        &:hover ~ div {
          color: $cool-gray;
        }
      }
    }
  }
</style>

Upvotes: 0

Hasan Ismayilov
Hasan Ismayilov

Reputation: 45

There is actually no selector that can select the previous sibling in css. But it is possible to use certain tricks. For example, if you want to change the style of the previous element when you hover over any element, you can use this:

<!DOCTYPE html>
<html lang="en">
<head>
  <style>
    .element:has(+ .next-element:hover){
      /* here your style for .element */
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="element"></div>
    <div class="next-element"></div>
  </div>
</body>
</html>

In this situation if you hover over .next-element the style of .element will change as you defined above

Upvotes: 3

The Myth
The Myth

Reputation: 1218

Though there is no previous CSS selector. I've found a quick and easy method to select one yourself. Here is the HTML markup:

<div class="parent">
    <div class="child-1"></div>
    <div class="child-2"></div>
</div>

In your JavaScript simply do:

document.querySelector(".child-2").parentElement.querySelector(".child-1")

This will first select the parent div and later on select the child-1 div from the child-2 div.

If you are using jQuery you can simply do:

$(".child-2").prev()

Upvotes: -4

Bahtiyar &#214;zdere
Bahtiyar &#214;zdere

Reputation: 1918

You can use :has() as following.

.thePrevious:has(+ .theNextSibling)

I used this for fixing overlapping bootstrap modals as follows. Any previous modals will be hidden if there are multiple.

.modal.show.modal--open:has(~ .modal.show.modal--open){
   opacity: 0;
 }

Upvotes: 5

Harshil Modi
Harshil Modi

Reputation: 436

My requirement was to select currently hovered item's previous and next two siblings with the help of @Quentin 's answer I selected previous siblings.

.child{
  width: 25px;
  height: 25px;
}

.child:hover {
    background:blue;
}

 .child:has( + .child:hover) {
  background: yellow;
}

.child:has(+ .child + .child:hover){
  background:green;
}

.child:hover + .child {
  background: red;
}

.child:hover + .child + .child {
  background: magenta;
}
<ul class="parent">
  <li class="child"></li>
  <li class="child"></li>
  <li class="child"></li>
  <li class="child"></li>
  <li class="child"></li>
  <li class="child"></li>
</ul>

To select all previous siblings

.child {
    width: 25px;
    height: 25px;
}

.child:hover {
    background: blue;
}

.child:has(~ .child:hover) {
    background: red;
}
<ul class="parent">
  <li class="child"></li>
  <li class="child"></li>
  <li class="child"></li>
  <li class="child"></li>
  <li class="child"></li>
  <li class="child"></li>
</ul>

Upvotes: 10

Roko C. Buljan
Roko C. Buljan

Reputation: 206627

Three tricks:
basically, reversing the HTML order of your elements in HTML,
and using the ~ Next siblings operator:

1. Using CSS Flex and row-reverse

.reverse {
  display: inline-flex;
  flex-direction: row-reverse;
}
.reverse span:hover ~ span { /* On SPAN hover target its "previous" elements */
  background:gold;
}
Hover a SPAN and see the previous elements being styled!<br>

<div class="reverse">
  <!-- Reverse the order of inner elements -->
  <span>5</span>
  <span>4</span>
  <span>3</span>
  <span>2</span>
  <span>1</span>
</div>

2. Using Flex with direction: RTL

.reverse {
  display: inline-flex;
  direction: rtl;
}
.reverse span:hover ~ span { /* On SPAN hover target its "previous" elements */
  background: red;
}
Hover a SPAN and see the previous elements being styled!<br>

<div class="reverse">
  <!-- Reverse the order of inner elements -->
  <span>5</span>
  <span>4</span>
  <span>3</span>
  <span>2</span>
  <span>1</span>
</div>

3. Using float right

.reverse { 
  display: inline-block;
}
.reverse span{
  float: right; 
}
.reverse span:hover ~ span { /* On SPAN hover target its "previous" elements */
  background: red;
}
Hover a SPAN and see the previous elements being styled!<br>

<div class="reverse">
  <!-- Reverse the order of inner elements -->
  <span>5</span>
  <span>4</span>
  <span>3</span>
  <span>2</span>
  <span>1</span>
</div>

Upvotes: 36

tolypash
tolypash

Reputation: 125

I fixed this problem by putting my elements in a flexbox and then using flex-direction: column-reverse.

Then I had to invert my elements in the HTML manually (put them in reverse order), and it looked normal and it worked!

<div style="display: flex; flex-direction: column-reverse">
  <a class="element2">Element 2</a>
  <a class="element1">Element 1</a>
</div>
...
<style>
  .element2:hover + element1 {
    ...
  }
</style>

Upvotes: 3

Nerius Jok
Nerius Jok

Reputation: 3257

For my use case was needed to change previous element style on focus and hover only having 2 items in parent element. to do so used :focus-within and :hover pseudo-classes.

like so selecting whenever focus/hover event occurs

.root-element:hover .element-to-style { background-color: red;} 
.root-element:focus-within .element-to-style { background-color: green;} 
<div class="root-element">
<span class="element-to-style"> TextFocused</span>
<input type="text" placeholder="type To Style"/>

</div>

Upvotes: 0

meghahere
meghahere

Reputation: 131

Overriding the styles of next siblings on hover, so that it looks like only previous siblings have styles added on hover.

ul li {
  color: red;
}

ul:hover li {
  color: blue;
}

ul:hover li:hover ~ li{
  color: red;
}
<ul>
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
</ul>

Upvotes: 7

Roma Kim
Roma Kim

Reputation: 371

I've found the easiest solution. It might only apply based on what you're doing.

Let's say you want to hover on "sibling_2" to change "sibling_1" in the example below:

<div class='parent'>
  <div class='sibling_1'></div>
  <div class='sibling_2'></div>
</div>

Since there's no previous element selector you can simply switch 'sibling_1' and 'sibling_2' around and apply so they look the same.

.parent {
  display: flex;
  flex-direction: row-reverse;
}

Now you can select them like that.

.sibling_1:hover ~ .sibling_2 {
  #your CSS
}

Upvotes: 4

R-D
R-D

Reputation: 634

/* Add a style to all the children, then undo the style to the target 
and sibling children of your target. */

ul>li {
  color: red;
}

ul>li.target,
ul>li.target~li {
  color: inherit;
}
<ul>
  <li>before</li>
  <li class="target">target</li>
  <li>after</li>
  <li>after</li>
</ul>

Upvotes: 4

Dandgerson
Dandgerson

Reputation: 89

There is no such selector, but in the DOM API has a pretty read-only property

Node.previousSibling

Upvotes: 2

Victor Gorban
Victor Gorban

Reputation: 1699

There's not "previous selector", but you can use the combination of :not and ~ ("after selector"). No reverse order, no javascript.

.parent a{
  color: blue
}

.parent a.active{
  color: red
}

.parent a:not(.parent a.active ~ a){
  color: red
}
<div class="parent">
  <a href="">link</a>
  <a href="">link</a>
  <a href="" class="active">link</a>
  <a href="">link</a>
  <a href="">link</a>
</div>

I think my approach is more straight-forward than "style all divs, than remove styling for after divs", or using javascript, or using reverse order.

Upvotes: 48

Carel
Carel

Reputation: 3397

You could use double negation

SELECTOR:not([SELECTOR]FILTER):not([SELECTOR]FILTER + SELECTOR) { ... }

Replace SELECTOR with either the TAG or .CLASS ( Using #ID is probably too specific ). Replace FILTER with some other :PSUEDO-SELECTOR (I've only tried :hover) or .CLASS (More for toggling through Javascript).

Since the typical usage will probably rely upon hovering (See example that follows)

/* Effect only limited when hovering */
TAG.CLASS:not(TAG.CLASS:hover):not(TAG.CLASS:hover + TAG.CLASS) {}
/* Effect only applied when hovering */
PARENT.CLASS:hover > CHILD.CLASS:not(CHILD.CLASS:hover):not(CHILD.CLASS:hover + CHILD.CLASS) {}

/* Solution */
div.parent:hover > div.child:not(:hover):not(:hover ~ .child)  {
    background-color:red;
    border-radius:1.5em;
}
div.parent:hover > div.child:not(:hover):not(:hover ~ .child) > div {
    background-color:yellow;
}

/* Make pretty (kinda) */
div.parent {
  width:9em;
  height:9em;
  /* Layout */
  display:grid;
  grid-template-columns : auto auto auto;
  grid-template-rows : auto auto auto;
}
div.child {
  /* Dimensions */
  height:3em;
  width:3em;
  /* Layout */
  position:relative;
  /* Cursor */
  cursor: pointer;
  /* Presentation */
  border: 1px black solid;
  border-radius:1.5em;
}
.star {
  /* Dimensions */
  width: 2.5em;
  height: 2.5em;
  /* Placement */
  position:absolute;
  top: 50%;
  left: 50%;
  transform:translate(-50%,-50%);
  /* Geometry */
  -webkit-clip-path: polygon(
    50% 0%,
    63% 38%,
    100% 38%,
    69% 59%,
    82% 100%,
    50% 75%,
    18% 100%,
    31% 59%,
    0% 38%,
    37% 38%
  );
  clip-path: polygon(
    50% 0%,
    63% 38%,
    100% 38%,
    69% 59%,
    82% 100%,
    50% 75%,
    18% 100%,
    31% 59%,
    0% 38%,
    37% 38%
  );
  /* Presentation */
  background-color: lightgrey;
}
div.child:hover {
    /* Presentation */
    background-color:yellow;
    border-radius:1.5em;
}
div.child:hover > div.star {
    /* Presentation */
    background-color:red;
}
<div class="parent">
  <div class="child" href="#"><div class="star"></div></div>
  <div class="child" href="#"><div class="star"></div></div>
  <div class="child" href="#"><div class="star"></div></div>
  <div class="child" href="#"><div class="star"></div></div>
  <div class="child" href="#"><div class="star"></div></div>
  <div class="child" href="#"><div class="star"></div></div>
  <div class="child" href="#"><div class="star"></div></div>
  <div class="child" href="#"><div class="star"></div></div>
  <div class="child" href="#"><div class="star"></div></div>
 </div>

Upvotes: 12

user7153178
user7153178

Reputation:

I had this same problem, while I was trying to change prepend icon fill color on input focus, my code looked something like this:

<template #append>
    <b-input-group-text><strong class="text-danger">!</strong></b-input-group-text>
</template>
<b-form-input id="password_confirmation" v-model="form.password_confirmation" type="password" placeholder="Repeat password" autocomplete="new-password" />

The problem was that I'm using a vue-bootstrap slot to inject the prepend, so even if i change the location still get rendered after the input

Well my solution was to swipe their location, and add custom prepend and used ~ symbol, as css doesn't support previous sibling.

<div class="form-input-prepend">
    <svg-vue icon="common.lock" />
</div>
<b-form-input id="password_confirmation" v-model="form.password_confirmation" type="password" placeholder="Repeat password" autocomplete="new-password" />

Scss style

.form-control:focus ~ .form-input-prepend {
    svg path {
        fill: $accent;
    }
}

So just try to change its position, and if necessary use css order or position: absolute; to achieve what you want, and to avoid using javascript for this kind of needs.

Upvotes: -1

Jackal
Jackal

Reputation: 3521

here is the link for a similar question

CSS select all previous siblings for a star rating

So I post my solution using bits of everyones responses and anyone can use it as reference and possibliy recommend improvements.

// Just to check input value
// Consts
const starRadios = document.querySelectorAll('input[name="rating"]');

// EventListeners
starRadios.forEach((radio) => radio.addEventListener('change', getStarRadioValue));

// Get star radio value
function getStarRadioValue(event) { 
    alert(event.target.value) 
    // Do something with it
};
.star-rating {
  font-size: 1.5rem;
  unicode-bidi: bidi-override;
  direction: rtl;
  text-align: left;
}
.star-rating.editable label:hover {
  cursor: pointer;
}
.star-rating.editable .icon-star:hover,
.star-rating.editable .icon-star:hover ~ .icon-star {
  background-color: #fb2727 !important;
}

.icon-star {
  position: relative;
  background-color: #72747d;
  width: 32px;
  height: 32px;
  display: inline-block;
  transition: background-color 0.3s ease;
}
.icon-star.filled {
  background-color: #fb2727;
}

.icon-star > label {
  display: inline-block;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  position: absolute;
}

.icon-star > label > input[type="radio"] {
  position: absolute;
  top: 0;
  left: 0;
  transform: translateY(50%) translateX(50%);
  display: none;
}
<div class="star-rating editable">
  <span class="icon-star">
    <label>
      <input type="radio" name="rating"  value="5" />
    </label>
  </span>
  <span class="icon-star">
    <label>
      <input type="radio" name="rating" value="4" />
    </label>
  </span>
  <span class="icon-star">
    <label>
      <input type="radio" name="rating" value="3" />
    </label>
  </span>
  <span class="icon-star">
    <label>
      <input type="radio" name="rating" value="2" />
    </label>
  </span>
  <span class="icon-star">
    <label>
      <input type="radio" name="rating"  value="1" />
    </label>
  </span>
</div>

Upvotes: 1

Ron16
Ron16

Reputation: 473

There isn't, and there is.

If you must place the label before the input, just place the label after the input and keep both the label & the input inside a div, and style the div as following :

.input-box {
  display: flex;
  flex-direction: column-reverse;
}
<div class="input-box">
  <input
   id="email"
   class="form-item"           
   />

   <label for="email" class="form-item-header">                  
   E-Mail*                  
   </label>
</div>

Now you can apply the standard next sibling styling options available in css, and it will appear like you are using a previous sibling styling.

Upvotes: 3

kyle
kyle

Reputation: 527

I needed a solution to select the previous sibling tr. I came up with this solution using React and Styled-components. This is not my exact solution (This is from memory, hours later). I know there is a flaw in the setHighlighterRow function.

OnMouseOver a row will set the row index to state, and rerender the previous row with a new background color

class ReactClass extends Component {
  constructor() {
    this.state = {
       highlightRowIndex: null
    }
  }

  setHighlightedRow = (index) => {
    const highlightRowIndex = index === null ? null : index - 1;
    this.setState({highlightRowIndex});
  }

  render() {
    return (
       <Table>
        <Tbody>
           {arr.map((row, index) => {
                const isHighlighted = index === this.state.highlightRowIndex
                return {
                    <Trow 
                        isHighlighted={isHighlighted}
                        onMouseOver={() => this.setHighlightedRow(index)}
                        onMouseOut={() => this.setHighlightedRow(null)}
                        >
                        ...
                    </Trow>
                }
           })}  
        </Tbody>   
       </Table>
    )
  }
}

const Trow = styled.tr`
    & td {
        background-color: ${p => p.isHighlighted ? 'red' : 'white'};
    }

    &:hover {
        background-color: red;
    }
`;

Upvotes: 2

SherylHohman
SherylHohman

Reputation: 17980

No. It is not possible via CSS. It takes the "Cascade" to heart ;-).


However, if you are able to add JavaScript to your page, a little bit of jQuery could get you to your end goal.
You can use jQuery's find to perform a "look-ahead" on your target element/class/id, then backtrack to select your target.
Then you use jQuery to re-write the DOM (CSS) for your element.

Based on this answer by Mike Brant, the following jQuery snippet could help.

$('p + ul').prev('p')

This first selects all <ul>s that immediately follow a <p>.
Then it "backtracks" to select all the previous <p>s from that set of <ul>s.

Effectively, "previous sibling" has been selected via jQuery.
Now, use the .css function to pass in your CSS new values for that element.


In my case I was looking to find a way to select a DIV with the id #full-width, but ONLY if it had a (indirect) descendant DIV with the class of .companies.

I had control of all the HTML under .companies, but could not alter any of the HTML above it.
And the cascade goes only 1 direction: down.

Thus I could select ALL #full-widths.
Or I could select .companies that only followed a #full-width.
But I could not select only #full-widths that proceeded .companies.

And, again, I was unable to add .companies any higher up in the HTML. That part of the HTML was written externally, and wrapped our code.

But with jQuery, I can select the required #full-widths, then assign the appropriate style:

$("#full-width").find(".companies").parents("#full-width").css( "width", "300px" );

This finds all #full-width .companies, and selects just those .companies, similar to how selectors are used to target specific elements in standard in CSS.
Then it uses .parents to "backtrack" and select ALL parents of .companies,
but filters those results to keep only #fill-width elements, so that in the end,
it only selects a #full-width element if it has a .companies class descendant.
Finally, it assigns a new CSS (width) value to the resulting element.

$(".parent").find(".change-parent").parents(".parent").css( "background-color", "darkred");
div {
  background-color: lightblue;
  width: 120px;
  height: 40px;
  border: 1px solid gray;
  padding: 5px;
}
.wrapper {
  background-color: blue;
  width: 250px;
  height: 165px;
}
.parent {
  background-color: green;
  width: 200px;
  height: 70px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>
<div class="wrapper">

  <div class="parent">
    "parent" turns red
    <div class="change-parent">
    descendant: "change-parent"
    </div>
  </div>
  
  <div class="parent">
    "parent" stays green
    <div class="nope">
    descendant: "nope"
    </div>
  </div>
  
</div>
Target <b>"<span style="color:darkgreen">parent</span>"</b> to turn <span style="color:red">red</span>.<br>
<b>Only</b> if it <b>has</b> a descendant of "change-parent".<br>
<br>
(reverse cascade, look ahead, parent un-descendant)
</html>

jQuery Reference Docs:
$() or jQuery(): DOM element.
.find: Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element.
.parents: Get the immediately preceding sibling of each element in the set of matched elements. If a selector is provided, it retrieves the previous sibling only if it matches that selector (filters the results to only include the listed elements/selectors).
.css: Set one or more CSS properties for the set of matched elements.

Upvotes: 6

Rounin
Rounin

Reputation: 29511

+ is for the next sibling. Is there an equivalent for the previous sibling?

You can use the two axe selectors: ! and ?

There are 2 subsequent sibling selectors in conventional CSS:

  • + is the immediate subsequent sibling selector
  • ~ is the any subsequent sibling selector

In conventional CSS, there is no previous sibling selector.

However, in the axe CSS post-processor library, there are 2 previous sibling selectors:

  • ? is the immediate previous sibling selector (opposite of +)
  • ! is the any previous sibling selector (opposite of ~)

Working Example:

In the example below:

  • .any-subsequent:hover ~ div selects any subsequent div
  • .immediate-subsequent:hover + div selects the immediate subsequent div
  • .any-previous:hover ! div selects any previous div
  • .immediate-previous:hover ? div selects the immediate previous div

div {
  display: inline-block;
  width: 60px;
  height: 100px;
  color: rgb(255, 255, 255);
  background-color: rgb(255, 0, 0);
  text-align: center;
  vertical-align: top;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.6s ease-out;
}

code {
  display: block;
  margin: 4px;
  font-size: 24px;
  line-height: 24px;
  background-color: rgba(0, 0, 0, 0.5);
}

div:nth-of-type(-n+4) {
  background-color: rgb(0, 0, 255);
}

div:nth-of-type(n+3):nth-of-type(-n+6) {
  opacity: 1;
}

.any-subsequent:hover ~ div,
.immediate-subsequent:hover + div,
.any-previous:hover ! div,
.immediate-previous:hover ? div {
  opacity: 1;
}
<h2>Hover over any of the blocks below</h2>

<div></div>
<div></div>

<div class="immediate-previous">Hover for <code>?</code> selector</div>
<div class="any-previous">Hover for <code>!</code> selector</div>
<div class="any-subsequent">Hover for <code>~</code> selector</div>
<div class="immediate-subsequent">Hover for <code>+</code> selector</div>

<div></div>
<div></div>

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

Upvotes: 35

0x1gene
0x1gene

Reputation: 3458

There is no official way to do that at the moment but you can use a little trick to achieve this ! Remember that it is experimental and it has some limitation ... (check this link if you worries about navigator compatibility )

What you can do is use a CSS3 selector : the pseudo classe called nth-child()

#list>* {
  display: inline-block;
  padding: 20px 28px;
  margin-right: 5px;
  border: 1px solid #bbb;
  background: #ddd;
  color: #444;
  margin: 0.4em 0;
}

#list :nth-child(-n+4) {
  color: #600b90;
  border: 1px dashed red;
  background: orange;
}
<p>The oranges elements are the previous sibling li selected using li:nth-child(-n+4)</p>

<div id="list">
  <span>1</span><!-- this will be selected -->
  <p>2</p><!-- this will be selected -->
  <p>3</p><!-- this will be selected -->
  <div>4</div><!-- this will be selected -->
  <div>5</div>
  <p>6</p>
  <p>7</p>
  <p>8</p>
  <p>9</p>
</div>

Limitations

  • You can't select previous elements based on the classes of the next elements
  • This is the same for pseudo classes

Upvotes: 19

Michael Benjamin
Michael Benjamin

Reputation: 372099

Consider the order property of flex and grid layouts.

I'll focus on flexbox in the examples below, but the same concepts apply to Grid.


With flexbox, a previous sibling selector can be simulated.

In particular, the flex order property can move elements around the screen.

Here's an example:

You want element A to turn red when element B is hovered.

<ul>
    <li>A</li>
    <li>B</li>
</ul>

STEPS

  1. Make the ul a flex container.

    ul { display: flex; }
    

  1. Reverse the order of siblings in the mark-up.

    <ul>
       <li>B</li>
       <li>A</li>
    </ul>
    

  1. Use a sibling selector to target Element A (~ or + will do) .

    li:hover + li { background-color: red; }
    

  1. Use the flex order property to restore the order of siblings on the visual display.

    li:last-child { order: -1; }
    

...and voilĂ ! A previous sibling selector is born (or at least simulated).

Here's the full code:

ul {
    display: flex;
}

li:hover + li {
    background-color: red;
}

li:last-child {
    order: -1;
}

/* non-essential decorative styles */
li {
    height: 200px;
    width: 200px;
    background-color: aqua;
    margin: 5px;
    list-style-type: none;
    cursor: pointer;
}
<ul>
    <li>B</li>
    <li>A</li>
</ul>

From the flexbox spec:

5.4. Display Order: the order property

Flex items are, by default, displayed and laid out in the same order as they appear in the source document. The order property can be used to change this ordering.

The order property controls the order in which flex items appear within the flex container, by assigning them to ordinal groups. It takes a single <integer> value, which specifies which ordinal group the flex item belongs to.

The initial order value for all flex items is 0.

Also see order in the CSS Grid Layout spec.


Examples of "previous sibling selectors" created with the flex order property.

.container { display: flex; }

.box5 { order: 1; }    
.box5:hover + .box4 { background-color: orangered; font-size: 1.5em; }

.box6 { order: -4; }
.box7 { order: -3; }
.box8 { order: -2; }
.box9 { order: -1; }
.box9:hover ~ :not(.box12):nth-child(-1n+5) { background-color: orangered;
                                              font-size: 1.5em; }
.box12 { order: 2; }
.box12:hover ~ :nth-last-child(-1n+2) { background-color: orangered;
                                        font-size: 1.5em; }
.box21 { order: 1; }
.box21:hover ~ .box { background-color: orangered; font-size: 1.5em; }

/* non-essential decorative styles */
.container {
    padding: 5px;
    background-color: #888;
}
.box {
    height: 50px;
    width: 75px;
    margin: 5px;
    background-color: lightgreen;
    display: flex;
    justify-content: center;
    align-items: center;
    text-align: center;
    cursor: pointer;
}
<p>
Using the flex <code>order</code> property to construct a previous sibling selector
</p>

<div class="container">
    <div class="box box1"><span>1</span></div>
    <div class="box box2"><span>2</span></div>
    <div class="box box3"><span>3</span></div>
    <div class="box box5"><span>HOVER ME</span></div>
    <div class="box box4"><span>4</span></div>
</div>

<br>

<div class="container">
    <div class="box box9"><span>HOVER ME</span></div>
    <div class="box box12"><span>HOVER ME</span></div>
    <div class="box box6"><span>6</span></div>
    <div class="box box7"><span>7</span></div>
    <div class="box box8"><span>8</span></div>
    <div class="box box10"><span>10</span></div>
    <div class="box box11"><span>11</span></div>
</div>

<br>

<div class="container">
    <div class="box box21"><span>HOVER ME</span></div>
    <div class="box box13"><span>13</span></div>
    <div class="box box14"><span>14</span></div>
    <div class="box box15"><span>15</span></div>
    <div class="box box16"><span>16</span></div>
    <div class="box box17"><span>17</span></div>
    <div class="box box18"><span>18</span></div>
    <div class="box box19"><span>19</span></div>
    <div class="box box20"><span>20</span></div>
</div>

jsFiddle


A Side Note – Two Outdated Beliefs about CSS

Flexbox is shattering long-held beliefs about CSS.

One such belief is that a previous sibling selector is not possible in CSS.

To say this belief is widespread would be an understatement. Here's a sampling of related questions on Stack Overflow alone:

As described above, this belief is not entirely true. A previous sibling selector can be simulated in CSS using the flex order property.

The z-index Myth

Another long-standing belief has been that z-index works only on positioned elements.

In fact, the most current version of the spec – the W3C Editor's Draft – still asserts this to be true:

9.9.1 Specifying the stack level: the z-index property

z-index

  • Value: auto | | inherit
  • Initial: auto
  • Applies to: positioned elements
  • Inherited: no
  • Percentages: N/A
  • Media: visual
  • Computed value: as specified

(emphasis added)

In reality, however, this information is obsolete and inaccurate.

Elements that are flex items or grid items can create stacking contexts even when position is static.

4.3. Flex Item Z-Ordering

Flex items paint exactly the same as inline blocks, except that order-modified document order is used in place of raw document order, and z-index values other than auto create a stacking context even if position is static.

5.4. Z-axis Ordering: the z-index property

The painting order of grid items is exactly the same as inline blocks, except that order-modified document order is used in place of raw document order, and z-index values other than auto create a stacking context even if position is static.

Here's a demonstration of z-index working on non-positioned flex items: https://jsfiddle.net/m0wddwxs/

Upvotes: 214

Hejar
Hejar

Reputation: 110

I had a similar problem and found out that all problem of this nature can be solved as follows:

  1. give all your items a style.
  2. give your selected item a style.
  3. give next items a style using + or ~.

and this way you'll be able to style your current, previous items(all items overridden with current and next items) and your next items.

example:

/* all items (will be styled as previous) */
li {
  color: blue;
}

/* the item i want to distinguish */
li.milk {
  color: red;
}

/* next items */
li ~ li  {
  color: green;
}


<ul>
  <li>Tea</li>
  <li class="milk">Milk</li>
  <li>Juice</li>
  <li>others</li>
</ul>

Hope it helps someone.

Upvotes: 1

Vadim Ovchinnikov
Vadim Ovchinnikov

Reputation: 14022

Another flexbox solution

You can use inverse the order of elements in HTML. Then besides using order as in Michael_B's answer you can use flex-direction: row-reverse; or flex-direction: column-reverse; depending on your layout.

Working sample:

.flex {
  display: flex;
  flex-direction: row-reverse;
   /* Align content at the "reversed" end i.e. beginning */
  justify-content: flex-end;
}

/* On hover target its "previous" elements */
.flex-item:hover ~ .flex-item {
  background-color: lime;
}

/* styles just for demo */
.flex-item {
  background-color: orange;
  color: white;
  padding: 20px;
  font-size: 3rem;
  border-radius: 50%;
}
<div class="flex">
  <div class="flex-item">5</div>
  <div class="flex-item">4</div>
  <div class="flex-item">3</div>
  <div class="flex-item">2</div>
  <div class="flex-item">1</div>
</div>

Upvotes: 21

DynamicDan
DynamicDan

Reputation: 423

Depending on your exact objective, there is a way to achieve the usefulness of a parent selector without using one (even if one were to exist)...

Say we have:

<div>
  <ul>
    <li><a>Pants</a></li>
    <li><a>Socks</a></li>
    <ul>
      <li><a>White socks</a></li>
      <li><a>Blue socks</a></li>
    </ul>
  </ul>
</div>

What can we do to make the Socks block (including sock colours) stand out visually using spacing?

What would be nice but doesn't exist:

ul li ul:parent {
  margin-top: 15px;
  margin-bottom: 15px;
}

What does exist:

li > a {
  margin-top: 15px;
  display: block;
}
li > a:only-child {
  margin-top: 0px;
}

This sets all anchor links to have 15px margin on the top and resets it back to 0 for those with no UL elements (or other tags) inside LIs.

Upvotes: 3

Related Questions