Skip to main content

Rating v1.0.0

Allow user to evaluate content by selecting a rating, typically displayed as stars or other visual indicators.

Default Setup Anchor (Default Setup)

Your rating:
<fieldset class="rating">

  <legend>Your rating:</legend>

  <div class="rating__container">

    <div class="rating__item">

      <input type="radio" name="rating" value="1" id="rating-1">
      <label for="rating-1"><span>1 Star</span></label>

    </div>

    <div class="rating__item">

      <input type="radio" name="rating" value="2" id="rating-2">
      <label for="rating-2"><span>2 Stars</span></label>

    </div>

    <div class="rating__item">

      <input type="radio" name="rating" value="3" id="rating-3">
      <label for="rating-3"><span>3 Stars</span></label>

    </div>

    <div class="rating__item">

      <input type="radio" name="rating" value="4" id="rating-4">
      <label for="rating-4"><span>4 Stars</span></label>

    </div>

    <div class="rating__item">

      <input type="radio" name="rating" value="5" id="rating-5">
      <label for="rating-5"><span>5 Stars</span></label>

    </div>

  </div>

</fieldset>
/*!

  Radancy Component Library: Rating
  Contributor(s): 
  Michael "Spell" Spellacy

  Dependencies: Sass

*/

.rating {

  --checked-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='50' height='50' viewBox='0 0 50 50'%3e%3cpath fill='blueviolet' stroke='%23666' stroke-width='2' d='m25,1 6,17h18l-14,11 5,17-15-10-15,10 5-17-14-11h18z'/%3e%3c/svg%3e");
  --max-stars: 5;
  --unchecked-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='50' height='50' viewBox='0 0 50 50'%3e%3cpath fill='%23fff' stroke='%23666' d='m25,1 6,17h18l-14,11 5,17-15-10-15,10 5-17-14-11h18z'/%3e%3c/svg%3e");

  border: 0;
  margin: 0;
  padding: 0;
  width: fit-content;

  &__container {
  display: grid;
  gap: .8em;
  grid-template-columns: repeat(var(--max-stars), 1fr);
  }

  legend {
  font-weight: bold;
  margin-bottom: .5em;
  }

  label {
  background-image: var(--unchecked-image); 
  background-position: center;
  background-repeat: no-repeat;
  background-size: contain;
  display: block;
  height: calc(60em/16);
  margin: 0;
  padding: 0;
  width: calc(60em/16);
  }

  .active {

    label {
    background-image: var(--checked-image); 
    }

    // Make sure there is always a visible focus state when cycling through input element.

    input {

      &:focus-visible {

        + label {
        outline: 2px solid black;
        outline-offset: 2px;
        }

      }

    }

  }

  // Accessibly hide input and span inside label.

  input, label span {
  clip: rect(0 0 0 0); 
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap; 
  width: 1px;
  }

}
/*!

  Radancy Component Library: Rating

  Contributor(s):
  Michael "Spell" Spellacy

*/

(function() {

    "use strict";
  
    // Display which Disclosure is in use via console:
  
    console.log("%cRating%cv1.0.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");

    // Select all rating items

    var ratingItems = document.querySelectorAll(".rating__item");

    // Initial check for any checked input and set up event listeners

    ratingItems.forEach(function(ratingItem, index) {

        var input = ratingItem.querySelector("input[type='radio']");

        // If input is checked on load, apply selected classes

        if (input.checked) {

            applySelectedClasses(index);

        }

        // Add event listeners for change, hover, and focus

        input.addEventListener("change", function() {

            applySelectedClasses(index);

        });

        ratingItem.addEventListener("mouseenter", function() {

            applyActiveClasses(index);

        });

        ratingItem.addEventListener("focusin", function() {

            applyActiveClasses(index);

        });

        ratingItem.addEventListener("mouseleave", conditionalRemoveActiveClasses);
        ratingItem.addEventListener("focusout", conditionalRemoveActiveClasses);

    });

    // Function to add "selected" and "active" class up to the selected item

    function applySelectedClasses(currentIndex) {

        // First, clear all classes to reset the selection

        ratingItems.forEach(function(ratingItem, index) {

            ratingItem.classList.remove("active", "selected");

            if (index <= currentIndex) {

                ratingItem.classList.add("active", "selected");

            }

        });

    }

    // Function to temporarily add "active" class up to the current item on hover/focus

    function applyActiveClasses(currentIndex) {

        ratingItems.forEach(function(ratingItem, index) {

            // Apply "active" only if index is up to the current index

            // Preserve "selected" items even when applying "active"

            ratingItem.classList.toggle("active", index <= currentIndex || ratingItem.classList.contains("selected"));

        });

    }

    // Function to remove "active" classes added by hover/focus if not "selected"

    function conditionalRemoveActiveClasses() {

        ratingItems.forEach(function(ratingItem) {

            if (!ratingItem.classList.contains("selected")) {

                ratingItem.classList.remove("active");

            }

        });

    }
  
})();

Report Issues

Find a bug? Want a new feature? Report it on JIRA. You're so awesome!