The HTML includes a <form>
with a rating widget, and two buttons. The rating widget includes a <fieldset>
, <legend>
, a <div> containing five radio buttons. Users can select one of the radio buttons to rate the project.
<form>
<fieldset class="rating">
<legend>Rate this project:</legend>
<div>
<input type="radio" name="rating" value="1" aria-label="1 star" required/>
<input type="radio" name="rating" value="2" aria-label="2 stars"/>
<input type="radio" name="rating" value="3" aria-label="3 stars"/>
<input type="radio" name="rating" value="4" aria-label="4 stars"/>
<input type="radio" name="rating" value="5" aria-label="5 stars"/>
</div>
</fieldset>
<input type="reset">
<input type="submit" value="Submit a 5-star review">
</form>
We style the rating widget using CSS. The radio buttons will be made to look like stars.
The stars are all light grey by default. When the user hovers over the stars, the potential value is displayed via dark and light starts. When a rating is made, the stars are yellow and transparent. We also use CSS to disable submission if you don't rate this project as well as it deserved to be rated.
The HTML includes a project rating widget within a <form>
. The form is submittable when you rate the project correctly.
The rating <fieldset>
groups the five radio buttons, displayed as stars, with the <legend>
informing the user of the purpose of the widget. We've included five same-named <input type="radio">
buttons. By using the same name, the user can only select one element in the group. We did not include a <label>
, so included the aria-label
attribute to provides a label for screen reader users. The required
attribute means the user must select one of the radio buttons or the form will not pass constraint validation and will not be submittable until a radio button is selected. (In other words, the widget is invalid until a value is selected. This means we can use the :invalid
pseudo-class.)
Once a radio button in a same-named group of radio button is selected, you can't return the group to an unchecked state without JS or resetting the form. so, we include a reset button created using an <input>
of type reset
. We also include a submit button, which we will disable if the wrong option is selected.
This CSS-only widget is all possible thanks to various input-state pseudo-classes.
We start by hiding the radio buttons and replacing them with stars:
/* make the current radio visually hidden */
input[type="radio"] {
appearance: none;
margin: 0;
box-shadow: none;
}
/* replace with a star */
input[type=radio]::after {
content: '\2605';
font-size: 32px;
}
The stars are all light grey by default and darker grey on hover.
The pseudo-class that does the heavy lifting in this example is
:invalid
, which matches any input that has an invalid
value. Because of the required
attribute, we can use the :invalid
pseudo-class.
Since the generated stars are underpinned by radio buttons in a single set, the radio buttons are all invalid until one of them is selected, thus giving a valid value to the set. The stars are colored using:
input[type=radio]:invalid::after {
color: #ddd;
}
In order to make the stars turn dark from the first up to whichever
one is being hovered or focsused by the user, we continue to use
:invalid
in conjunction with interaction pseudo-classes and
the adjacent-sibling combinator:
div:hover input[type=radio]:invalid::after,
div:focus-within input[type=radio]:invalid::after {
color: #888;
}
div:hover input[type=radio]:hover ~ input[type=radio]:invalid::after,
div input[type=radio]:focus ~ input[type=radio]:invalid::after {
color: #ddd;
}
The first rule sets all the stars to be a medium gray as long as the
div is hovered or has focus within itself. The second rule sets all the
stars after the star being hovered/focused back to light gray,
by saying, in effect, “if an input of type=radio
is
hovered/focused, select all the following sibling inputs with
type=radio
that are invalid and then style their
::after
content”.
(The previous two rules could be compressed down to a single rule
using :has()
, but that makes things more complicated and is
a bit beyond the scope of this tutorial.)
If a star is clicked/selected, this activates the underpinning radio button and gives the set of radio buttons a valid value. Thus:
div input[type=radio]:valid {
color: orange;
}
This sets all five stars to be orange. The stars that follow whichever was clicked/selected then need to be made hollow, which is accomplished using a rule similar to the one we saw earlier that set all the stars after the hovered/focused star to be light gray:
div input[type=radio]:checked ~ input[type=radio]:not(:checked)::after{
color: #ccc;
content: '\2606'; /* optional. hollow star */
}
Here we used :checked
to find the radio button that was
activated by the click/select action, and then used the
following-sibling combinator to grab all the following radio buttons
that are not checked.