Examples
Basic usage
<div class="rating" role="radiogroup" aria-label="Rate this">
<input type="radio" id="r1-1" name="rating-1" value="1" />
<label for="r1-1"><span class="visually-hidden">1 star</span></label>
<input type="radio" id="r1-2" name="rating-1" value="2" />
<label for="r1-2"><span class="visually-hidden">2 stars</span></label>
<input type="radio" id="r1-3" name="rating-1" value="3" />
<label for="r1-3"><span class="visually-hidden">3 stars</span></label>
<input type="radio" id="r1-4" name="rating-1" value="4" />
<label for="r1-4"><span class="visually-hidden">4 stars</span></label>
<input type="radio" id="r1-5" name="rating-1" value="5" />
<label for="r1-5"><span class="visually-hidden">5 stars</span></label>
</div>
Pre-selected value
<div class="rating" role="radiogroup" aria-label="Rate this">
<input type="radio" id="r2-1" name="rating-2" value="1" />
<label for="r2-1"><span class="visually-hidden">1 star</span></label>
<input type="radio" id="r2-2" name="rating-2" value="2" />
<label for="r2-2"><span class="visually-hidden">2 stars</span></label>
<input type="radio" id="r2-3" name="rating-2" value="3" checked />
<label for="r2-3"><span class="visually-hidden">3 stars</span></label>
<input type="radio" id="r2-4" name="rating-2" value="4" />
<label for="r2-4"><span class="visually-hidden">4 stars</span></label>
<input type="radio" id="r2-5" name="rating-2" value="5" />
<label for="r2-5"><span class="visually-hidden">5 stars</span></label>
</div>
Read-only display
<div class="rating is-readonly" role="img" aria-label="Rated 4 out of 5 stars">
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star"></span>
</div>
Sizes
<div class="stack">
<div class="rating is-sm is-readonly" role="img" aria-label="Rated 4 out of 5 stars">
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star"></span>
</div>
<div class="rating is-readonly" role="img" aria-label="Rated 4 out of 5 stars">
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star"></span>
</div>
<div class="rating is-lg is-readonly" role="img" aria-label="Rated 4 out of 5 stars">
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star"></span>
</div>
</div>
Custom icon
<div
class="rating is-readonly"
role="img"
aria-label="Rated 4 out of 5"
style="
--rating-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/%3E%3C/svg%3E");
--rating-color-active: var(--color-danger);
"
>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star"></span>
</div>
Custom colors
<div class="stack">
<div
class="rating is-readonly"
role="img"
aria-label="Rated 3 out of 5 stars"
style="--rating-color-active: var(--color-success)"
>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star"></span>
<span class="star"></span>
</div>
<div
class="rating is-readonly"
role="img"
aria-label="Rated 4 out of 5 stars"
style="--rating-color-active: var(--color-primary)"
>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star"></span>
</div>
</div>
Source comments
Rating -- star rating with CSS-only interaction (extra).
The visual fill of stars uses :has() to track the checked /
hovered position. Inputs are written in natural ascending order
(1 first, 5 last) and the CSS selectors fill "this label and all
preceding labels". Two mutually-exclusive branches via
:not(:has(label:hover)) on the selection rule:
1. Selection (active when NO label is hovered):
:not(:has(label:hover)) input:checked + label
:not(:has(label:hover)) label:has(~ input:checked)
Both fill with --rating-color-active. Stays visible when the
cursor is in the gap between stars (rating hovered but
no label hovered) -- the :has() guard checks for actual
label hover, not container hover.
2. Hover trail (active when ANY label is hovered):
label:hover the hovered label
label:has(~ label:hover) every earlier label
Fills with --rating-color-active. Selection rule deactivates
automatically because its :not(:has) guard is now false.
Star shape: a CSS mask-image clips a colored block to the SVG
silhouette. The icon is a data-URI token override -- swap to a
heart, thumb, or any single-path SVG without changing markup.
Markup (interactive):
<div class="rating" role="radiogroup" aria-label="Rating">
<input type="radio" id="r1" name="r" value="1">
<label for="r1"><span class="visually-hidden">1 star</span></label>
<input type="radio" id="r2" name="r" value="2">
<label for="r2"><span class="visually-hidden">2 stars</span></label>
<input type="radio" id="r3" name="r" value="3" checked>
<label for="r3"><span class="visually-hidden">3 stars</span></label>
<input type="radio" id="r4" name="r" value="4">
<label for="r4"><span class="visually-hidden">4 stars</span></label>
<input type="radio" id="r5" name="r" value="5">
<label for="r5"><span class="visually-hidden">5 stars</span></label>
</div>
Markup (read-only):
<div class="rating is-readonly" role="img"
aria-label="Rated 4 out of 5 stars">
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star is-filled"></span>
<span class="star"></span>
</div>
Implementation note: uses `:has()` over natural LTR markup --
the older `direction: rtl` + descending-DOM-order trick (5 → 1)
is clever but breaks keyboard arrow direction in some browsers.
`:has()` keeps source order natural and arrow keys correct.
Deferred:
Half-star variant (4.5 / 5) -- consider adding `.star.is-half`
with a partial-fill mask or gradient when a real consumer
needs it. Skip for v1 to keep surface minimal.
Reference
Source: src/css/extras/rating.css
Applies to
.rating
Classes
| Class | Kind | Description |
|---|---|---|
.is-readonly | variant | Display-only mode using span elements instead of radio inputs |
.is-sm | variant | Small size variant (1rem stars) |
.is-lg | variant | Large size variant (2rem stars) |
Tokens
| Token | Default | Description |
|---|---|---|
--rating-size | 1.5rem | Inline and block size of each star |
--rating-icon | url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22 12 18.56 5.82 22 7 14.14l-5-4.87 6.91-1.01z'/%3E%3C/svg%3E") | Mask image for the star shape, swappable to any single-path SVG |
--rating-color | var(--color-border) | Color of unselected stars |
--rating-color-active | var(--color-warning) | Color of selected and hovered stars |
--rating-gap | var(--ui-gutter-compact) | Gap between stars |