Style class by attribute value (nth-child)

Viewed 1446

I want to append style on a class depending on the value in the parents data-attribute.

In my case, rating starts.

If data-rating is for ex. 3, the first three starts should be filled with a color.

I assume nth-child will do the trick, but i can't manage it to work..

This is my html:

<div class="container">
    <div class="rating" data-rating="3">
        <span class="star" data-score='1'></span> /* Filled */
        <span class="star" data-score='2'></span> /* Filled */
        <span class="star" data-score='3'></span> /* Filled */
        <span class="star" data-score='4'></span>
        <span class="star" data-score='5'></span>
    </div>
</div>

I'm using sass.

Thx!

3 Answers

Since CSS support for reading values from data- attributes are still extremely limited, you cannot rely on reading the data-rating to dynamically apply :nth-child rules. However, since you are using a CSS preprocessor (SASS as you mentioned), you can use a @for loop to generate all the necessary selectors required. The caveat to this solution is that you will need to pre-determinate the maximum rating (in this case, it is 5).

The SASS code below (also available as a gist) will do more or less what you're intending to achieve. To select for the first x elements, you can use the :nth-child(-n+x) selector rule.

  1. Use the SASS @for loop to iterate through all possible star ratings (1 through x, where x is 5 in your case)
  2. Generate a selector that targets the .rating[data-rating='x']
  3. In each .rating selector, you use the nth-child trick above to style the first x stars.
// Some basic configuration
$stars: 5;
$star-color: #FFD464;

// Baes styles for unfilled stars
.rating {
  .star {
    color: #ccc;
  }
}

// Create custom nth-child selectors for each data-rating value
@for $i from 1 through $stars {
  .rating[data-rating='#{$i}'] {
    .star:nth-child(-n+#{$i}) {
      color: $star-color;
    }
  }
}

Disclaimer: The only major drawback of this method is that it gets bloated quickly when you have too many stars. Having 5 stars is not an issue, since you will only generate 5 different sets of rules (plus with the adent of gzipping and CSS minification, the code bloat does not incur a heavy overhead). Imagine if you have a 10-star rating system, the selector rules will be overly verbose: in that case, you might want to consider a JS-based solution.

Here is a proof-of-concept example using the compiled SASS code above:

.rating .star {
  color: #ccc;
}

.rating[data-rating='1'] .star:nth-child(-n+1) {
  color: #FFD464;
}

.rating[data-rating='2'] .star:nth-child(-n+2) {
  color: #FFD464;
}

.rating[data-rating='3'] .star:nth-child(-n+3) {
  color: #FFD464;
}

.rating[data-rating='4'] .star:nth-child(-n+4) {
  color: #FFD464;
}

.rating[data-rating='5'] .star:nth-child(-n+5) {
  color: #FFD464;
}
<div class="container">
    <div class="rating" data-rating="1">
        <span class="star" data-score='1'>&#9733;</span>
        <span class="star" data-score='2'>&#9733;</span>
        <span class="star" data-score='3'>&#9733;</span>
        <span class="star" data-score='4'>&#9733;</span>
        <span class="star" data-score='5'>&#9733;</span>
    </div>
    <div class="rating" data-rating="2">
        <span class="star" data-score='1'>&#9733;</span>
        <span class="star" data-score='2'>&#9733;</span>
        <span class="star" data-score='3'>&#9733;</span>
        <span class="star" data-score='4'>&#9733;</span>
        <span class="star" data-score='5'>&#9733;</span>
    </div>
    <div class="rating" data-rating="3">
        <span class="star" data-score='1'>&#9733;</span>
        <span class="star" data-score='2'>&#9733;</span>
        <span class="star" data-score='3'>&#9733;</span>
        <span class="star" data-score='4'>&#9733;</span>
        <span class="star" data-score='5'>&#9733;</span>
    </div>
    <div class="rating" data-rating="4">
        <span class="star" data-score='1'>&#9733;</span>
        <span class="star" data-score='2'>&#9733;</span>
        <span class="star" data-score='3'>&#9733;</span>
        <span class="star" data-score='4'>&#9733;</span>
        <span class="star" data-score='5'>&#9733;</span>
    </div>
    <div class="rating" data-rating="5">
        <span class="star" data-score='1'>&#9733;</span>
        <span class="star" data-score='2'>&#9733;</span>
        <span class="star" data-score='3'>&#9733;</span>
        <span class="star" data-score='4'>&#9733;</span>
        <span class="star" data-score='5'>&#9733;</span>
    </div>
</div>


Even better: Use pseudo-elements to change star glyphs

If you want to use different glyphs for filled and unfilled stars, that is also possible. You simply use the ::before (or ::after) pseudo-element to inject the correct glyph conditionally (\2605 for filled star, \2606 for empty star) , again using the same iterative logic as above:

// Some basic configuration
$stars: 5;
$star-color: #FFD464;
$stars-filled: "\2605";
$stars-empty: "\2606";

// Baes styles for unfilled stars
.rating {
  .star {
    color: #ccc;
    &::before {
      content: $stars-empty;
    }
  }
}

// Create custom nth-child selectors for each data-rating value
@for $i from 1 through $stars {
  .rating[data-rating='#{$i}'] {
    .star:nth-child(-n+#{$i}) {
      color: $star-color;

      &::before {
        content: $stars-filled;
      }
    }
  }
}

@charset "UTF-8";
.rating .star {
  color: #ccc;
}
.rating .star::before {
  content: "☆";
}

.rating[data-rating='1'] .star:nth-child(-n+1) {
  color: #FFD464;
}
.rating[data-rating='1'] .star:nth-child(-n+1)::before {
  content: "★";
}

.rating[data-rating='2'] .star:nth-child(-n+2) {
  color: #FFD464;
}
.rating[data-rating='2'] .star:nth-child(-n+2)::before {
  content: "★";
}

.rating[data-rating='3'] .star:nth-child(-n+3) {
  color: #FFD464;
}
.rating[data-rating='3'] .star:nth-child(-n+3)::before {
  content: "★";
}

.rating[data-rating='4'] .star:nth-child(-n+4) {
  color: #FFD464;
}
.rating[data-rating='4'] .star:nth-child(-n+4)::before {
  content: "★";
}

.rating[data-rating='5'] .star:nth-child(-n+5) {
  color: #FFD464;
}
.rating[data-rating='5'] .star:nth-child(-n+5)::before {
  content: "★";
}
<div class="container">
    <div class="rating" data-rating="1">
        <span class="star" data-score='1'></span>
        <span class="star" data-score='2'></span>
        <span class="star" data-score='3'></span>
        <span class="star" data-score='4'></span>
        <span class="star" data-score='5'></span>
    </div>
    <div class="rating" data-rating="2">
        <span class="star" data-score='1'></span>
        <span class="star" data-score='2'></span>
        <span class="star" data-score='3'></span>
        <span class="star" data-score='4'></span>
        <span class="star" data-score='5'></span>
    </div>
    <div class="rating" data-rating="3">
        <span class="star" data-score='1'></span>
        <span class="star" data-score='2'></span>
        <span class="star" data-score='3'></span>
        <span class="star" data-score='4'></span>
        <span class="star" data-score='5'></span>
    </div>
    <div class="rating" data-rating="4">
        <span class="star" data-score='1'></span>
        <span class="star" data-score='2'></span>
        <span class="star" data-score='3'></span>
        <span class="star" data-score='4'></span>
        <span class="star" data-score='5'></span>
    </div>
    <div class="rating" data-rating="5">
        <span class="star" data-score='1'></span>
        <span class="star" data-score='2'></span>
        <span class="star" data-score='3'></span>
        <span class="star" data-score='4'></span>
        <span class="star" data-score='5'></span>
    </div>
</div>

You can try this.

This bit of sass code all you need. CODEPEN

 @for $i from 1 through 5 {
  .rating[data-rating="#{$i}"] {
      .fa-star:nth-child(-n+#{$i}) {
          color: yellow;
      }
  }
}

Check out the compiled css and full code.

body {
  background: #333;
}

.container {
  margin: 25px 50px;
}

.rating[data-rating="1"] .fa-star:nth-child(-n+1) {
  color: yellow;
}

.rating[data-rating="2"] .fa-star:nth-child(-n+2) {
  color: yellow;
}

.rating[data-rating="3"] .fa-star:nth-child(-n+3) {
  color: yellow;
}

.rating[data-rating="4"] .fa-star:nth-child(-n+4) {
  color: yellow;
}

.rating[data-rating="5"] .fa-star:nth-child(-n+5) {
  color: yellow;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<!--  When data rating is 3 -->
<div class="container">
    <div class="rating" data-rating="3">
        <span class="fa fa-star" data-score='1'></span> 
        <span class="fa fa-star" data-score='2'></span> 
        <span class="fa fa-star" data-score='3'></span> 
        <span class="fa fa-star" data-score='4'></span>
        <span class="fa fa-star" data-score='5'></span>
    </div>
</div>

<!--  When data rating is 2 -->
<div class="container">
    <div class="rating" data-rating="2">
        <span class="fa fa-star" data-score='1'></span> 
        <span class="fa fa-star" data-score='2'></span> 
        <span class="fa fa-star" data-score='3'></span> 
        <span class="fa fa-star" data-score='4'></span>
        <span class="fa fa-star" data-score='5'></span>
    </div>
</div>


<!--  When data rating is 1 -->
<div class="container">
    <div class="rating" data-rating="1">
        <span class="fa fa-star" data-score='1'></span> 
        <span class="fa fa-star" data-score='2'></span> 
        <span class="fa fa-star" data-score='3'></span> 
        <span class="fa fa-star" data-score='4'></span>
        <span class="fa fa-star" data-score='5'></span>
    </div>
</div>

Sass is a css generator. There's no interaction between sass and html like in js and html. So it's not possible from sass to go inside the html and take an attribute value.

What you need is a js/jquery solution.

for example: $(".star:nth-child(-n+"+$(".rating").attr("data-rating")+")").css("background","red")

Related