Rating
A .re-rating is a <fieldset> of visually-hidden radios (one per value) paired
with star <label>s — so keyboard selection, single-choice, and form value are
all native, with no JavaScript. Add a value-0 radio labelled “No rating” to allow
clearing. CSS-only.
The fieldset is direction: rtl as an implementation detail: the radios are in
DOM order high→low so the floor-safe sibling selectors can fill the chosen star
and all lower ones, and rtl flips both the visual order (to 1→5 left-to-right)
and the arrow keys back into alignment (Right/Up select higher, Left/Down lower).
Stars stay a low→high-left-to-right scale regardless of page direction.
Native arrow-key direction in a reversed radio group varies by browser
(Chromium honors rtl, WebKit doesn’t). For consistent arrows everywhere, opt
into the tiny enhanceRating behavior — it intercepts the arrow keys so
Right/Up always raise and Left/Down always lower, identically across browsers.
The CSS-only base stays fully usable without it.
import { enhanceRating } from "@relements/core/behaviors/rating";enhanceRating(document);Interactive
Section titled “Interactive”<fieldset class="re-rating" aria-label="Rate your experience">
<input class="re-sr-only" type="radio" name="rating" id="rate-5" value="5" />
<label class="re-rating__star" for="rate-5" aria-label="5 stars">★</label>
<input class="re-sr-only" type="radio" name="rating" id="rate-4" value="4" />
<label class="re-rating__star" for="rate-4" aria-label="4 stars">★</label>
<input class="re-sr-only" type="radio" name="rating" id="rate-3" value="3" />
<label class="re-rating__star" for="rate-3" aria-label="3 stars">★</label>
<input class="re-sr-only" type="radio" name="rating" id="rate-2" value="2" />
<label class="re-rating__star" for="rate-2" aria-label="2 stars">★</label>
<input class="re-sr-only" type="radio" name="rating" id="rate-1" value="1" />
<label class="re-rating__star" for="rate-1" aria-label="1 star">★</label>
<input class="re-sr-only" type="radio" name="rating" id="rate-0" value="0" checked />
<label class="re-sr-only" for="rate-0">No rating</label>
</fieldset> data-size="sm" or "lg" on the fieldset.
<fieldset class="re-rating" data-size="sm" aria-label="Small rating">
<input class="re-sr-only" type="radio" name="r-sm" id="sm-3" value="3" checked />
<label class="re-rating__star" for="sm-3" aria-label="3 stars">★</label>
<input class="re-sr-only" type="radio" name="r-sm" id="sm-2" value="2" />
<label class="re-rating__star" for="sm-2" aria-label="2 stars">★</label>
<input class="re-sr-only" type="radio" name="r-sm" id="sm-1" value="1" />
<label class="re-rating__star" for="sm-1" aria-label="1 star">★</label>
</fieldset>
<fieldset
class="re-rating"
data-size="lg"
aria-label="Large rating"
style="margin-block-start: var(--re-space-4)"
>
<input class="re-sr-only" type="radio" name="r-lg" id="lg-3" value="3" checked />
<label class="re-rating__star" for="lg-3" aria-label="3 stars">★</label>
<input class="re-sr-only" type="radio" name="r-lg" id="lg-2" value="2" />
<label class="re-rating__star" for="lg-2" aria-label="2 stars">★</label>
<input class="re-sr-only" type="radio" name="r-lg" id="lg-1" value="1" />
<label class="re-rating__star" for="lg-1" aria-label="1 star">★</label>
</fieldset> Read-only display
Section titled “Read-only display”For showing an average (no input), use .re-rating-display with role="img", an
aria-label, and --re-rating-value — the filled overlay is clipped to
value / max, so fractional values render a partial star.
<span
class="re-rating-display"
role="img"
aria-label="Rated 3.5 out of 5"
data-stars="★★★★★"
style="--re-rating-value: 3.5"
>★★★★★</span
> Accessibility
Section titled “Accessibility”- Keyboard — it’s a native radio group: Tab moves into the group (landing on
the checked star, or the first if none), and the arrow keys move the selection
within it. Because the group is reversed, native arrow direction varies by
browser; opt into
enhanceRatingand it normalizes to ArrowRight/ArrowUp = next-higher, ArrowLeft/ArrowDown = next-lower (by value, identically everywhere). From the cleared “No rating” state both arrows enter at the lowest star, and the value-0 and disabled radios are skipped during arrow navigation. - Focus —
:focus-visibleon the focused (visually hidden) radio paints the--re-shadow-focusring on its visible star. - Semantics — the
<fieldset>carries anaria-labelnaming the rating, and each star<label>has anaria-label(“5 stars” … “1 star”) so the choices announce as a labelled radio group rather than bare glyphs. The read-only display is a singlerole="img"with anaria-labellike “Rated 3.5 out of 5” — the stars are decorative text behind it. - Notes — the radios and the value-0 “No rating” label use
.re-sr-only, so the clear option is reachable and announced without showing a sixth star; thedirection: rtlon the fieldset is presentational only and the stars stay a low→high left-to-right scale regardless of page direction. Disabled via the native:disabled/aria-disabled="true". Under forced colors (HCM) the filled stars repaint with the systemHighlightcolor and the focus ring becomes a realoutlineon the visible star, since the sr-only radio can’t show one. See the accessibility guide.