DEV Community

Cover image for Creating a Multicolored Star Rating Card Component
sahra πŸ’«
sahra πŸ’«

Posted on

Creating a Multicolored Star Rating Card Component

The Idea Behind

I remember using an application sometime back and the review request popped up, it was just the plain 5 golden stars and a basic textarea. I simply canceled it and said to myself "Giving reviews is so boring, maybe if the review system looked more fun I would give reviews more oftenπŸ˜…. I bet I could make something more interesting". Yep, that's how the idea for this project was bornπŸ’‘. Sheer laziness produced the need to create something more complex but at least more interesting.
Timmy from southpark looking at the mirror asking himself what is wrong with him

Well the main point I had in mind when creating this project, was to showcase how making the star rating interface multicolored could enhance user experience, making it more visually appealing, which adds an element of excitement, and promotes more engagement for the rating process.

In this article, I'll walk you through how I created the multicolored star rating card component which also supports fractional values rating.
Below is a preview of what we will be creating:
preview
Without further time wasting, let's dive in.

Setting up the HTML structure

To get started, we first include the header section with the project's title.

<header>
  <span>&star;</span>
  <h1>Interactive Star Rating</h1>
  <span>&star;</span>
</header>
Enter fullscreen mode Exit fullscreen mode

Next, inside the main tag, we need to create a container div and inside this div, we include 2 other divs to hold the front and back sections of the card.

<main>
  <div id="rating-card">
     <div class="card front">
     </div>

    <div class="card back">
    </div>
  </div>
</main>
Enter fullscreen mode Exit fullscreen mode

Inside the card front div, we include the card heading as well as a div container for the emojis and a p element to display the rating texts.

<h2>Please Rate Us</h2>

<div id="emoticon-container">&nbsp;</div>
<p id="satisfaction-text">&nbsp;</p>
Enter fullscreen mode Exit fullscreen mode

After that, in the card front div, we create another div that holds the rating stars, we'll be making use of HTML entities, for the star icons. We'll then create two sets of stars, filled and empty stars. In between these sets, we are going to include a blank slider, we will get to the purpose of this slider in a bit.

<div id="rating-component-container">
  <div class="filled-stars">
    <span>&starf;</span>
    <span>&starf;</span>
    <span>&starf;</span>
    <span>&starf;</span>
    <span>&starf;</span>
  </div>

  <div id="blank-slider"></div>

  <div class="empty-stars">
    <span>&star;</span>
    <span>&star;</span>
    <span>&star;</span>
    <span>&star;</span>
    <span>&star;</span>
  </div>           
</div>
Enter fullscreen mode Exit fullscreen mode

The next thing we need to include in the card front is the input field that will allow for keyboard and fractional value ratings, as well as the button that will be used to switch the card to the back.

<input type="number" id="val" step="0.1" min="0" max="10" placeholder="1 - 10">
<button id="wr-btn">Write Review</button> 
Enter fullscreen mode Exit fullscreen mode

That's all for the front section of our card, now let's move on to creating the card back. Inside the card back div, we first need to include an arrow Icon that will take us back to the front section of the card, and then include the information that will only be visible at the card back.

<span id="back-btn">⇦</span>
<h2>Write Review</h2>
<p>Please share your thoughts about our app, so we can serve you better.</p>
Enter fullscreen mode Exit fullscreen mode

Next, we include a textarea that will allow the user to leave written reviews along with a button that submits the overall rating.

<textarea id="review-field" placeholder="Write a review..." ></textarea>
<button id="rate-us">Rate Us</button>
Enter fullscreen mode Exit fullscreen mode

Now that we're done creating the rating card let's create one last section just before the main closing tag, to thank the user after their review has been sent.

<div class="sent">
  <h2> Review Sent! </h2>
  <p>Thank you for rating us.</p>
  <button id="go-back-btn">Go Back</button>
</div>
Enter fullscreen mode Exit fullscreen mode

That's all for our HTML file, now let's move into our CSS file to style the elements.

Styling to the card with CSS

To get started with our styling, we first need to declare the color variables that we will make use of throughout the stylesheet, as well as add some general styles to the document.

:root {
  --lightBlue: #43a6c6;
  --darkBlue: #03000f;
  --darkGray: #333;
  --starSize: 3.5em;
}

html {
  background-color: var(--darkBlue);
  color: whitesmoke;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
Enter fullscreen mode Exit fullscreen mode

Next, we style the header section of the page along with the h1 element.

header {
  display: flex;
  justify-content: space-between;
  padding: 25px;
  letter-spacing: 2px;
  position: sticky;
  top: 0;
  z-index: 2;
  border-bottom: 2px solid;
  background-color: var(--darkBlue);
  text-shadow: 3px 3px 5px var(--lightBlue);
  box-shadow: 10px 10px 20px -10px var(--lightBlue);
}

h1 {
  font-size: 2.5em;
}

header > span {
  font-size: 25px;
  color: #ffee00;
}
Enter fullscreen mode Exit fullscreen mode

Moving on, we style the main content section, as well as the rating-card container where we will set its transform-style property to preserve-3d. This ensures that the reversed front of the card is not visible when flipped and it only shows the card back.

main {
  min-height: 90vh;
  display: grid;
  place-items: center;
  perspective: 800px;
}

#rating-card {
  position: relative;
  box-shadow: 1px 1px 30px -1px var(--lightBlue);
  border-radius: 20px;
  transition: 2s;
  min-width: 250px;
  transform-style: preserve-3d; /* very important */
}
Enter fullscreen mode Exit fullscreen mode

Next up, we apply general styling for both the front and back cards.

.card {
  display: grid;
  place-items: center;
  padding: 25px 20px 40px;
  background-color: var(--darkBlue);
  border-radius: 20px;
}
Enter fullscreen mode Exit fullscreen mode

What we need to do next is to set the backface-visibility of the card front to hidden to prevent it from reflecting when flipped.

.front {
  backface-visibility: hidden;
}
Enter fullscreen mode Exit fullscreen mode

After that, we style the h2 and p tags on the card, along with the container for the expression emojis.

h2 {
  text-shadow: 1px 1px 3px var(--lightBlue);
  margin-bottom: 10px;
}

p {
  padding: 10px;
  color: #999;
}

#emoticon-container {
  font-size: var(--starSize);
  margin-bottom: 10px;
}
Enter fullscreen mode Exit fullscreen mode

Next, we style the rating stars container in the card.

#rating-component-container {
  position: relative;
  overflow: hidden;
  cursor: pointer;
  user-select: none;
}
Enter fullscreen mode Exit fullscreen mode

After that, we style the filled stars container, giving it a multicolored background image ranging from red to green, and clip the background image of the element to its text content, while setting the color of the filled stars in the container to transparent to reflect the background.

.filled-stars {
  background-image: linear-gradient(to right, red 5%, yellow, #00ff00);
  background-clip: text;
  -webkit-background-clip: text;
  transform: scale(1);
}

.filled-stars > span {
  font-size: var(--starSize);
  color: transparent;
}
Enter fullscreen mode Exit fullscreen mode

Next up, we style the blank-slider div, setting its width property to 100% and moving it to the absolute right of its container. When we get to the JavaScript section, you will understand why it's important that it's done this way.

#blank-slider {
  background-color: var(--darkBlue);
  position: absolute;
  height: 100%;
  right: 0; /*very important*/
  top: 0;
  width: 100%; /*very important*/
  transition: 3s;
}
Enter fullscreen mode Exit fullscreen mode

Style the empty-stars container, as well as the empty stars span elements.

.empty-stars {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 100%;
  transform: scale(1);
  transition: 1s;
}

.empty-stars > span {
  color: var(--darkGray);
  font-size: var(--starSize);
  cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

After that, we style the value input field along with its hover state.

#val {
  background-color: transparent;
  color: aliceblue;
  outline: none;
  padding: 8px;
  margin-top: 15px;
  letter-spacing: 2px;
  font-weight: 700;
  border-radius: 10px;
  border: 2px solid var(--darkGray);
  width: 80%;
}

#val:hover {
  border: 2px solid var(--lightBlue);
}
Enter fullscreen mode Exit fullscreen mode

That's all for the front section of the card.

Next up, we style two individual classes which we'll apply to the cards through JavaScript.

.p-events {
  pointer-events: none;
}

.flipped {
  transform: rotateY(180deg);
}
Enter fullscreen mode Exit fullscreen mode

Moving on, we get to styling the back view of the card, along with all it's contents.

.back {
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
  transform: rotateY(180deg);
  pointer-events: none;
}

#back-btn {
  color: var(--lightBlue);
  position: absolute;
  top: 10px;
  left: 12px;
  font-weight: 900;
  font-size: 25px;
  cursor: pointer;
}

#back-btn:hover {
  transform: scale(1.2);
}

.back textarea {
  border-radius: 10px;
  background-color: transparent;
  padding: 10px;
  height: 100px;
  color: #ccc;
  width: 90%;
  outline: none;
  border: 2px solid var(--darkGray);
  resize: none;
  letter-spacing: 1.5px;
  overflow: hidden;
  font-weight: 100;
  font-size: 12px;
}

.back > textarea:hover, button:hover {
  box-shadow: 1px 1px 20px -1px var(--lightBlue);
}
Enter fullscreen mode Exit fullscreen mode

Now that we're done styling the rating card, let's style all the button elements in the document, along with the popup "Thank you" note that shows after the "rate-us" button is clicked.

button {
  margin-top: 30px;
  border-radius: 10px;
  padding: 10px;
  background-color: var(--lightBlue);
  border: none;
  font-size: 0.9em;
  font-weight: 600;
}

.sent {
  display: none;
  place-items: center;
}
Enter fullscreen mode Exit fullscreen mode

Well, that's all for our CSS. quite a lot for such a little card right?πŸ˜…. Now let's add some functionality to the card.

Adding functionality with JavaScript

Inside our JavaScript file, we first need to get all the elements we'll be working with later on.

const blankSlider = document.getElementById("blank-slider");
const emptyStars = document.querySelector(".empty-stars");
const val = document.getElementById("val");
const wrBtn = document.getElementById("wr-btn");
const backBtn = document.getElementById("back-btn");
const cardFront = document.querySelector(".front");
const cardBack = document.querySelector(".back");
const ratingCard = document.getElementById("rating-card");
const satisfactionTxtContainer = document.getElementById("satisfaction-text");
const goBackBtn = document.getElementById("go-back-btn");
const sentInfo = document.querySelector(".sent");
const rateUsBtn = document.getElementById("rate-us");
const reviewField = document.getElementById("review-field");
const emoticonContainer = document.getElementById("emoticon-container");
Enter fullscreen mode Exit fullscreen mode

Next up, we need to declare three arrays, one for holding the different colors of the stars, the second to hold the HTML character codes for the emojis, and the third to hold the different texts that will be displayed according to the user's rating.

const hoverColors = ['red', 'orange', 'yellow', 'chartreuse', 'lime'];
const emoticons = ['&#128545;', '&#128577;', '&#128528;', '&#128522;', '&#129321;'];
const satisfactionTxt = ['Highly Unsatisfied', 'Slightly Unsatisfied', 'feeling Indifferent', 'Quite Satisfied', 'Extremely Satisfied'];
Enter fullscreen mode Exit fullscreen mode

Calculating and displaying the stars' colors

Firstly, we need to create a simple function that will be used to reverse numbers. I would get to the importance of this function in a bit, stay with me.

// Function to reverse numbers within 100
function reverseValue(value) {
  return 100 - value;
}
Enter fullscreen mode Exit fullscreen mode

Next up, we need to loop through all the empty stars elements.

// loop through all the empty stars element
for (let i = 0; i < emptyStars.children.length; i++) {
  // Stars color logic goes here.
}
Enter fullscreen mode Exit fullscreen mode

Inside this loop, we change the color of the hovered star on mouseenter and restore the default color of hovered star on mouseleave.

emptyStars.children[i].onmouseenter = (e) => {
    emptyStars.children[i].style.color = hoverColors[i];
  };

emptyStars.children[i].onmouseleave = (e) => {
    emptyStars.children[i].style.color = "#333";
  };
Enter fullscreen mode Exit fullscreen mode

Next up, we need to update the val input value, blankSlider, satisfactionText, and emoticons according to the empty star that was clicked. This way, the blankSlider width moves forward or backward, covering or revealing the filled stars underneath the empty stars.

emptyStars.children[i].onclick = (e) => {
// changes the value of the input field to the number rating for the clicked star.
    val.value = i * 2 + 2;

    // sets the width percentage value of the slider to the index value of the clicked star plus 1 times 20 in reverse
    blankSlider.style.width = `${reverseValue((i + 1) * 20)}%`;

satisfactionTxtContainer.innerHTML = satisfactionTxt[i];
    emoticonContainer.innerHTML = emoticons[i];
  };
}
Enter fullscreen mode Exit fullscreen mode

Now, we want to be able to manipulate the slider movements as well as the display of the text and emoji's through the number input field alone, so we attach an input event listener to the val input field, which will also allow the slider respond to fractional value ratings.

// Add input event listener to the input value(val) element
val.addEventListener('input', (e) => {
  // prevents negative values from being entered into the input field, setting the lowest possible value to 0
 if (val.value < 0) {
      val.value = 0;
    }

  // Update the slider width based on the input value
  blankSlider.style.width = `${reverseValue(val.value * 10)}%`;

// Converts the input value to a number 4 or less to represent the index value to use for the different arrays.
  const index = Math.min(Math.floor(val.value / 2 -0.5), 4);

// Updates the satisfaction text and emoticon based on the input value
satisfactionTxtContainer.innerHTML = index < 0 || val.value == "" ? '&nbsp;' : satisfactionTxt[index];

emoticonContainer.innerHTML = index < 0 || val.value == "" ? '&nbsp;' : emoticons[index];

})
Enter fullscreen mode Exit fullscreen mode

Next, we apply the flipped and p-events classes which we created in CSS earlier to the ratingCard and cardFront respectively on click of the "write review" button, and remove them on click of the arrow backBtn at the card back, which flips it back to the card front.

// Flips to the card back 
wrBtn.onclick=()=>{
  ratingCard.classList.add("flipped");
  cardFront.classList.add("p-events");
  cardBack.style.pointerEvents="visible";

}

// Flips to the card front
backBtn.onclick=()=>{
  ratingCard.classList.remove("flipped");
  cardFront.classList.remove("p-events")
  cardBack.style.pointerEvents= "none";
}
Enter fullscreen mode Exit fullscreen mode

After that, on click of the "rate us" button, we store the user's rating information in an object, which we can then send to our server, but in this case we just log the information to the console.

// user's rating info object.
let userRating = {
  ratingValue: "",
  satisfaction: "",
  review: "",
};

// store the user's rating info
rateUsBtn.onclick = () => {
  ratingCard.style.display = "none";
  sentInfo.style.display = "grid";
  userRating.ratingValue = val.value;
  userRating.satisfaction = satisfactionTxtContainer.textContent;
  userRating.review = reviewField.value;
  console.log(userRating);
}

// Removes the thank you note and goes back to the rating card.
goBackBtn.onclick = () => {
  ratingCard.style.display = "block";
  sentInfo.style.display = "none";
}
Enter fullscreen mode Exit fullscreen mode

With that, our multicolored star rating card is fully functionalπŸ€—.

Conclusion

That's all for this article. I hope you enjoyed following along guys. You can check out the complete implementation of this project in the codepen below:

Please don't forget to leave your comments on what you think about this, or if you have any questions or maybe need additional clarification about the implementation.

Also, if there is a tutorial you would like me to cover, please don't hesitate to leave it in the comment sections. Till then, happy coding!😊

Top comments (8)

Collapse
 
renancferro profile image
Renan Ferro

Wow, really cool @sarahokolo ! Thanks for sharing :)

Collapse
 
sarahokolo profile image
sahra πŸ’«

Thank you Renan :)

Collapse
 
erikgiovani profile image
Erik Giovani

Really amazing!

Collapse
 
sarahokolo profile image
sahra πŸ’«

Thanks Erik, glad you liked it :)

Collapse
 
michaeltharrington profile image
Michael Tharrington

Daaaang! It looks so good!! πŸ™Œ

Collapse
 
sarahokolo profile image
sahra πŸ’«

Thank you Michael πŸ€—

Collapse
 
dannyengelman profile image
Danny Engelman

The rating was already done with a Native Web Component: dev.to/dannyengelman/twinkle-twink...

Only needed to apply the colors on the <rect> elements.

Collapse
 
sarahokolo profile image
sahra πŸ’«

The native rating web component is quite different from what I have here.

Two things that majorly set them apart are:

  1. Mine can have star ratings with dynamic fractional values, not just switching it to a half-star.

  2. The ability of a star color to look like it's flowing into the next.

So while the native rating web component simply have the ability to set a half star and change the colors which is quite simple and gets the job done for most, mine does a bit more than that.