Skip to main content Accessibility Feedback

Resetting highlighting for our star-based rating system

Earlier this week, we added the ability to show what a rating would look for our star-based rating system when a user hovers or tabs over stars.

There’s a problem, though: it also removes highlighting when a star is already selected. Today, let’s look at how to fix it.

Resetting the selected star after hover or focus

When we stop hovering on or tab off of a .star, we should reset the highlighting.

If there’s a selected star, we should highlight it. If there isn’t, we should remove all highlighting.

We can use the same function to handle both situations. Let’s setup a function called resetSelected(). We’ll pass it into a mouseleave event listener for when we mouse off of a .star, and a blur event for when we tab off of a .star.

Both of these events require useCapture to be set to true to force bubbling.

var resetSelected = function (event) {
	// Our code will go here...
};

// Reset selected on mouse off and blur
document.addEventListener('mouseleave', resetSelected, true);
document.addEventListener('blur', resetSelected, true);

In our resetSelected() method, let’s first make sure that the event happened on a .rating form.

The mouseleave event will also fire on the document if you move your mouse out of the viewport, and closest() isn’t a method on that element, so it will throw an error. To prevent that, let’s also make sure event.target.closest exists before running it.

var resetSelected = function (event) {

	// Only run our code on .rating forms
	if (!event.target.closest) return;
	var form = event.target.closest('.rating');
	if (!form) return;

};

Next, we want to get all of the stars in our form, and the selected star if one exists.

We’ll pass .star[aria-pressed="true"] into form.querySelector() to find a selected star if one exists. If it does, we’ll get it’s [data-star] value as our selected index. Otherwise, we’ll use 0. For brevity, we’ll use a ternary operator to set this variable.

var resetSelected = function (event) {

	// Only run our code on .rating forms
	if (!event.target.closest) return;
	var form = event.target.closest('.rating');
	if (!form) return;

	// Get all stars in this form (only search in the form, not the whole document)
	// Convert them from a node list to an array
	// https://gomakethings.com/converting-a-nodelist-to-an-array-with-vanilla-javascript/
	var stars = Array.from(form.querySelectorAll('.star'));

	// Get an existing rating if there is one
	var selected = form.querySelector('.star[aria-pressed="true"]');
	var selectedIndex = selected ? parseInt(selected.getAttribute('data-star'), 10) : 0;

};

Finally, we’ll loop through each star, adding or removing highlighting as needed.

var resetSelected = function (event) {

	// Only run our code on .rating forms
	if (!event.target.closest) return;
	var form = event.target.closest('.rating');
	if (!form) return;

	// Get all stars in this form (only search in the form, not the whole document)
	// Convert them from a node list to an array
	// https://gomakethings.com/converting-a-nodelist-to-an-array-with-vanilla-javascript/
	var stars = Array.from(form.querySelectorAll('.star'));

	// Get an existing rating if there is one
	var selected = form.querySelector('.star[aria-pressed="true"]');
	var selectedIndex = selected ? parseInt(selected.getAttribute('data-star'), 10) : 0;

	// Loop through each star, and add or remove the `.selected` class to toggle highlighting
	stars.forEach(function (star, index) {
		if (index < selectedIndex) {
			// Selected star or before it
			// Add highlighting
			star.classList.add('selected');
		} else {
			// After selected star
			// Remove highlight
			star.classList.remove('selected');
		}
	});

};

And that’s it!

As always, you can find the full source code on GitHub.

What next?

Currently, users can change their rating, but not remove it altogether.

For next week, try to come up with a way to remove a rating. If you want, send me what you come up with. I’d love to see it.