Rating

.rating[role="img"]

Rating — stars, in two genuinely different components.

Examples

Read-only

View “Read-only” as
<div class="rating rating-display" role="img" aria-label="Rated 3.5 out of 5"
     style="--rating-value: 3.5">
  <span class="rating-star"></span>
  <span class="rating-star"></span>
  <span class="rating-star"></span>
  <span class="rating-star"></span>
  <span class="rating-star"></span>
</div>

A number someone else produced, so it is an image with one name. It takes fractional values; the half star is real, drawn from the same polygon as the whole ones rather than from a second glyph.

Interactive

View “Interactive” as
Rate your experience
<fieldset class="rating rating-input">
  <legend class="field-label">Rate your experience</legend>
  <input class="rating-radio" type="radio" id="demo-rate-1" name="demo-rate" value="1" />
  <label class="rating-star" for="demo-rate-1"><span class="sr-only">1 star</span></label>
  <input class="rating-radio" type="radio" id="demo-rate-2" name="demo-rate" value="2" />
  <label class="rating-star" for="demo-rate-2"><span class="sr-only">2 stars</span></label>
  <input class="rating-radio" type="radio" id="demo-rate-3" name="demo-rate" value="3" />
  <label class="rating-star" for="demo-rate-3"><span class="sr-only">3 stars</span></label>
  <input class="rating-radio" type="radio" id="demo-rate-4" name="demo-rate" value="4" />
  <label class="rating-star" for="demo-rate-4"><span class="sr-only">4 stars</span></label>
  <input class="rating-radio" type="radio" id="demo-rate-5" name="demo-rate" value="5" />
  <label class="rating-star" for="demo-rate-5"><span class="sr-only">5 stars</span></label>
</fieldset>

A real radio group: arrow keys, submission and the “3 of 5” announcement are the platform’s.

The hover preview is :has(). It is shown at the default size, like the read-only one above — this example used to carry .rating-lg and the two previews read as one component drawn at two sizes for no reason anybody could see.

The three sizes

View “The three sizes” as
<div class="rating rating-display rating-sm" role="img" aria-label="Rated 4 out of 5"
     style="--rating-value: 4">
  <span class="rating-star"></span>
  <span class="rating-star"></span>
  <span class="rating-star"></span>
  <span class="rating-star"></span>
  <span class="rating-star"></span>
</div>
<div class="rating rating-display" role="img" aria-label="Rated 4 out of 5"
     style="--rating-value: 4">
  <span class="rating-star"></span>
  <span class="rating-star"></span>
  <span class="rating-star"></span>
  <span class="rating-star"></span>
  <span class="rating-star"></span>
</div>
<div class="rating rating-display rating-lg" role="img" aria-label="Rated 4 out of 5"
     style="--rating-value: 4">
  <span class="rating-star"></span>
  <span class="rating-star"></span>
  <span class="rating-star"></span>
  <span class="rating-star"></span>
  <span class="rating-star"></span>
</div>

The size is --rating-size and the three classes only set it.

It applies to the read-only rating freely; on the interactive one, remember that each star is a target and .rating-sm takes it under the 24px floor, so use it only where the spacing exception applies. The star itself is one shape declared twice — a polygon() for the border-shape branch and the same polygon as a mask for the base one — because border-shape shapes the box and can therefore take a real border, and a mask cannot. Change one and change both; the file says so at the declaration.

Tokens 10

Level 2, declared on .rating itself. Set any of them on that selector to restyle this component without touching the skin.

Level 2 tokens declared by rating
TokenDefault
--rating-fill0%
--rating-index0
--rating-target-sizemax(var(--rating-size), var(--target-size-min, 1.5rem))
--rating-value0
--rating-maskurl("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cpath%20d%3D%22M46.82%2C6.24Q50.00%2C0.00%2053.18%2C6.24L62.72%2C24.92Q64.99%2C29.37%2069.93%2C30.16L90.64%2C33.45Q97.55%2C34.55%2092.61%2C39.50L77.79%2C54.34Q74.25%2C57.88%2075.03%2C62.82L78.30%2C83.54Q79.39%2C90.45%2073.15%2C87.28L54.46%2C77.77Q50.00%2C75.50%2045.54%2C77.77L26.85%2C87.28Q20.61%2C90.45%2021.70%2C83.54L24.97%2C62.82Q25.75%2C57.88%2022.21%2C54.34L7.39%2C39.50Q2.45%2C34.55%209.36%2C33.45L30.07%2C30.16Q35.01%2C29.37%2037.28%2C24.92Z%22%2F%3E%3C%2Fsvg%3E")
--rating-shapepolygon( 50% 0%, 65% 29.4%, 97.6% 34.5%, 74.3% 57.9%, 79.4% 90.5%, 50% 75.5%, 20.6% 90.5%, 25.7% 57.9%, 2.4% 34.5%, 35% 29.4% )
--rating-colorvar(--color-accent)
--rating-color-emptyvar(--color-border)
--rating-gapvar(--space-1)
--rating-size1.25rem

Variants and states

Variants

  • .rating-display
  • .rating-input
  • .rating-sm
  • .rating-lg

Inside it

  • .rating-star
  • .rating-radio

State it reads

Read from the platform, never mirrored into a class that could disagree with it.

  • :checked
  • :disabled
  • :focus
  • :hover
  • data-legacy

Before you ship it

What you have to do 2 requirements

  1. .rating-display needs role="img" and an aria-label that states the value and the maximum ("Rated 3.5 out of 5"). The stars are decoration and are hidden from assistive technology; without the label the rating is silent.
  2. .rating-input needs a <legend> naming the group, and every radio needs its own label text — <span class="sr-only">3 stars</span> inside the label. A label with nothing but a star in it has no accessible name.

src/css/components/rating.css · npx mostlycss add rating