<div class="container margin-top-md">
  <div class="rating js-rating">
    <label for="rating" class="margin-bottom-xxs form-label">Rate the product:</label>
    <select name="rating" id="rating" class="rating__select">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
      <option value="4">4</option>
      <option value="5">5</option>
    </select>
  
    <div class="rating__control is-hidden js-rating__control">
      <svg viewBox="0 0 24 24"><g><polygon points="12 1.489 15.09 7.751 22 8.755 17 13.629 18.18 20.511 12 17.261 5.82 20.511 7 13.629 2 8.755 8.91 7.751 12 1.489"></polygon></g></svg>
    </div>
  </div>
</div>
.js .rating__select {
  display: none;
}

.rating__list {
  display: inline-flex;
}

.rating__item {
  color: var(--color-primary);
  cursor: pointer;
}

.rating__icon {
  display: block;
  
  svg {
    display: block;
    height: 2em;
    width: 2em;
    fill: currentColor;
  }
}

.rating__item--checked ~ .rating__item {
  color: var(--color-contrast-low);
}

// focus + active
.rating__item {
  position: relative;
  
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    width: 100%;
    background-color: currentColor;
    border-radius: 50%;
    opacity: 0;
    transform: scale(0);
    transition: all .2s;
  }
  
  &:focus {
    outline: none;
    
    &::before {
      opacity: 0.2;
      transform: scale(1);
    }
  }
  
  &:active .rating__icon {
    transform: scale(0.8);
  }
}

.rating__icon {
  transition: transform 0.2s;
}

// hover
.rating__list:hover .rating__item {
  color: var(--color-primary);
}

.rating__item:hover ~ .rating__item {
  color: var(--color-contrast-low);
}
View Compiled
  var Rating = function(element) {
  this.element = element;
  this.control = this.element.getElementsByClassName('js-rating__control')[0];
  this.iconCode = this.control.children[0].outerHTML;
  this.maxRateValue = 5;
  
  initRatingHTML(this);
  this.controlItems = this.control.getElementsByClassName('js-rating__item');
  initRatingEvents(this);
};

function initRatingHTML(rating) {
  var list = '<li class="rating__item rating__item--checked is-hidden"></li>';
  for(var i = 0; i < rating.maxRateValue; i++) {
    list = list + getItemHTML(rating, i);
  }
  
  list = '<ul role="radiogroup" class="rating__list">'+list+'</ul>';
  // improve SR accessibility including a legend element
  var labelElement = rating.element.getElementsByTagName('label');
  if(labelElement.length > 0) {
    var legendElement = '<legend class="'+labelElement[0].getAttribute('class')+'">'+labelElement[0].textContent+'</legend>';
    list = '<fieldset>'+legendElement+list+'</fieldset>';
    Util.addClass(labelElement[0], 'is-hidden');
  }
  rating.control.innerHTML = list;
  Util.removeClass(rating.control, 'is-hidden');
};

function getItemHTML(rating, index) {
  var tabIndex = index == 0 ? '0' : '-1',
      label = index == 0 ? ' star' : ' stars';
  var item = '<li role="radio" aria-checked="false" class="rating__item js-rating__item" aria-label="'+(index + 1)+label+'" tabindex="'+tabIndex+'"><span class="rating__icon" aria-hidden="true">'+rating.iconCode+'</span></li>';
  return item;
};

function initRatingEvents(rating) {
  rating.control.addEventListener('click', function(event){
    selectNewRate(rating, event.target.closest('.js-rating__item'));
  });
  
  rating.control.addEventListener('keydown', function(event){
    if(event.key.toLowerCase() == 'arrowright') navigateRating(rating, 'next');
    else if(event.key.toLowerCase() == 'arrowleft') navigateRating(rating, 'prev');
    else if(event.key.toLowerCase() == ' ') selectNewRate(rating, document.activeElement.closest('.js-rating__item'));
  });
};

function navigateRating(rating, direction) {
  var index = Util.getIndexInArray(rating.controlItems, document.activeElement.closest('.js-rating__item'));
  index = direction == 'next' ? index + 1 : index - 1;
  selectNewRate(rating, rating.controlItems[index]);
};

function selectNewRate(rating, target) {
   if(!target) return;
  // reset older rate
  var selectedRate = rating.control.querySelector('.rating__item--checked');  
  Util.removeClass(selectedRate, 'rating__item--checked');
  Util.setAttributes(selectedRate, {'aria-checked': 'false', 'tabindex': '-1'});
  Util.setAttributes(rating.controlItems[0], {'tabindex': '-1'});
  // style for new seleted rate
  Util.addClass(target, 'rating__item--checked');
  Util.setAttributes(target, {'aria-checked': 'true', 'tabindex': '0'});
  target.focus();
};

new Rating(document.getElementsByClassName('js-rating')[0]);

External CSS

  1. https://codepen.io/codyhouse/pen/oNxLjqp.scss
  2. https://unpkg.com/codyhouse-framework/main/assets/css/style.css

External JavaScript

  1. https://unpkg.com/codyhouse-framework/main/assets/js/util.js