Rating

A rating element that provides a way to accept interactive ratings using stars or other custom svgs. It can use input radio buttons, so it can be used with any form.

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(&quot;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&quot;);
                --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

ClassKindDescription
.is-readonlyvariantDisplay-only mode using span elements instead of radio inputs
.is-smvariantSmall size variant (1rem stars)
.is-lgvariantLarge size variant (2rem stars)

Tokens

TokenDefaultDescription
--rating-size1.5remInline and block size of each star
--rating-iconurl("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-colorvar(--color-border)Color of unselected stars
--rating-color-activevar(--color-warning)Color of selected and hovered stars
--rating-gapvar(--ui-gutter-compact)Gap between stars