Skip to content

Rating

Rating is two components wearing one name. Ask for a score and it is a real <fieldset> of native radios — arrow keys, one tab stop, a native form post, none of it written here. Show a score and it is a picture, which is the only way a fill can stop inside an icon.

  • Stable
  • React 19
  • Native radio group
  • Fractional fill
  • 24px targets
Rate this order
Average — 4.3 of 5
Hearts
Half steps
<Rating name="score" label="How was the delivery?" helper="Optional, and it stays anonymous." />

That is the whole editable API. count defaults to 5, clearable to true, and the group’s name is generated when you do not pass one — native radios need a shared name to be a group at all, so the component invents one rather than ship five separate groups.

To show a score instead of asking for one, pass readOnly:

<Rating readOnly value={4.3} />

This is the fact everything else on the page follows from:

Editable readOnly
Root element <fieldset role="radiogroup" class="wayra-rating"> <div class="wayra-rating" data-readonly="true">
Controls one <input type="radio"> per score none
Accessible name the <legend>, or your aria-label role="img" on the items row, named by formatLabel
Fill values 0, 50 or 100 per icon anything, under precision
Forms posts a string does not participate

An editable rating cannot paint 4.3, because a radio can only be picked or not. A read-only one cannot be 4.3 and focusable, because there is nothing to focus. Keeping them as one component with one prop between them is what stops the two from drifting apart.

How was the delivery?

Optional, and it stays anonymous.

Code
<Rating
name="score"
label="How was the delivery?"
helper="Optional, and it stays anonymous."
/>
<fieldset role="radiogroup" class="wayra-rating" data-color="warning" data-size="md"><legend class="wayra-rating-legend">How was the delivery?</legend><div class="wayra-rating-items"><input class="wayra-rating-clear" type="radio" name="usage" value="" aria-label="No rating" /><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="usage" value="1" aria-label="1 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="usage" value="2" aria-label="2 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="usage" value="3" aria-label="3 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="usage" value="4" aria-label="4 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="usage" value="5" aria-label="5 of 5" /></label></span></div><p class="wayra-rating-hint">Optional, and it stays anonymous.</p></fieldset>

Everything a group needs is already in the platform, so there is no keyboard code in this component:

Key Behavior
Tab Reaches the row once — the chosen score, or “No rating” when there is none.
← → ↑ ↓ Moves through the row and selects as it goes. Native, and it wraps.
Space Selects the focused score.

A rating differs from a radio group in one behaviour, and it needs two affordances because one of them is invisible to the keyboard and the other to the pointer:

  • Click the score you are already on and the rating goes back to none. The browser fires no change for a radio that is already checked, so the component listens for the click.
  • ArrowLeft from the first icon reaches a visually hidden radio with value="" that sits before the row. It is checked while nothing is, which is also why Tab lands there.

Both are clearable, on by default. Turn it off and a score, once given, can only be changed:

<Rating name="score" clearable={false} />

readOnly is where the fractional fill lives. Each icon is drawn twice — an empty layer and a filled one — and the filled copy is clipped to an exact percentage, so 4.3 of 5 is four whole icons and one filled 30% of the way across, not “four and a half”.

precision is the rounding applied before that:

exact — 30% of the fifth icon
half — snapped to a half icon
full — rounded down to 4
Code
<Rating readOnly value={4.3} />
<Rating readOnly value={4.3} precision="half" />
<Rating readOnly value={4.3} precision="full" />
<div class="wayra-rating" data-color="warning" data-size="md" data-readonly="true"><span class="wayra-rating-legend">exact — 30% of the fifth icon</span><div class="wayra-rating-items" role="img" aria-label="4.3 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:30"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span></div></div>
<div class="wayra-rating" data-color="warning" data-size="md" data-readonly="true"><span class="wayra-rating-legend">half — snapped to a half icon</span><div class="wayra-rating-items" role="img" aria-label="4.3 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:50"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span></div></div>
<div class="wayra-rating" data-color="warning" data-size="md" data-readonly="true"><span class="wayra-rating-legend">full — rounded down to 4</span><div class="wayra-rating-items" role="img" aria-label="4.3 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span></div></div>
precision Rounds to 4.3 becomes Use it for
exact (default) nothing 4.3 an average with a number beside it
half the nearest half icon 4.5 a compact list where a decimal is noise
full the nearest whole icon 4 a summary that should not imply precision

The percentage is public: --wayra-rating-fill is an inline custom property on each item, 0 to 100, and the fill layer clips itself from it. Hand-written markup sets it the same way.

half splits every icon into two hits, so the row offers 0.5, 1, 1.5, … and the arrow keys take twice as many steps to cross it.

Half steps — 10 arrow-key stops
Whole steps — 5
Code
<Rating name="score" half label="Half steps" />
<Rating name="score" label="Whole steps" />
<fieldset role="radiogroup" class="wayra-rating" data-color="warning" data-size="md" data-half="true"><legend class="wayra-rating-legend">Half steps — 10 arrow-key stops</legend><div class="wayra-rating-items"><input class="wayra-rating-clear" type="radio" name="half-demo" value="" aria-label="No rating" /><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit" data-half="start"><input class="wayra-rating-input" type="radio" name="half-demo" value="0.5" aria-label="0.5 of 5" /></label><label class="wayra-rating-hit" data-half="end"><input class="wayra-rating-input" type="radio" name="half-demo" value="1" aria-label="1 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit" data-half="start"><input class="wayra-rating-input" type="radio" name="half-demo" value="1.5" aria-label="1.5 of 5" /></label><label class="wayra-rating-hit" data-half="end"><input class="wayra-rating-input" type="radio" name="half-demo" value="2" aria-label="2 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:50"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit" data-half="start"><input class="wayra-rating-input" type="radio" name="half-demo" value="2.5" aria-label="2.5 of 5" /></label><label class="wayra-rating-hit" data-half="end"><input class="wayra-rating-input" type="radio" name="half-demo" value="3" aria-label="3 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit" data-half="start"><input class="wayra-rating-input" type="radio" name="half-demo" value="3.5" aria-label="3.5 of 5" /></label><label class="wayra-rating-hit" data-half="end"><input class="wayra-rating-input" type="radio" name="half-demo" value="4" aria-label="4 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit" data-half="start"><input class="wayra-rating-input" type="radio" name="half-demo" value="4.5" aria-label="4.5 of 5" /></label><label class="wayra-rating-hit" data-half="end"><input class="wayra-rating-input" type="radio" name="half-demo" value="5" aria-label="5 of 5" /></label></span></div></fieldset>
<fieldset role="radiogroup" class="wayra-rating" data-color="warning" data-size="md"><legend class="wayra-rating-legend">Whole steps — 5</legend><div class="wayra-rating-items"><input class="wayra-rating-clear" type="radio" name="whole-demo" value="" aria-label="No rating" /><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="whole-demo" value="1" aria-label="1 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="whole-demo" value="2" aria-label="2 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="whole-demo" value="3" aria-label="3 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="whole-demo" value="4" aria-label="4 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="whole-demo" value="5" aria-label="5 of 5" /></label></span></div></fieldset>

The half row paints exactly like the whole one at the same size — same icon, same cell. The two halves split the 24px cell, so each is ~12px wide and sits under the WCAG 2.5.8 pointer floor on purpose: making the halves clear it means either a cell twice the icon (dead space between stars wider than the icon itself) or a 48px icon (a half-star row a different size from every control beside it), and both were tried and rejected. The star stays a full-size target, and the precise path is the keyboard — the arrow keys step by halves on it.

half is editable-only. A read-only average rounds through precision instead — the two would otherwise be two ways to spell the same thing.

Code
<Rating readOnly value={3.5} color="primary" />
<Rating readOnly value={3.5} color="secondary" />
<Rating readOnly value={3.5} color="accent" />
<Rating readOnly value={3.5} color="neutral" />
<Rating readOnly value={3.5} color="info" />
<Rating readOnly value={3.5} color="success" />
<Rating readOnly value={3.5} color="warning" />
<Rating readOnly value={3.5} color="error" />
<svg style="display:none" aria-hidden="true"><symbol id="hu-star" viewBox="0 0 24 24"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></symbol><symbol id="hu-heart" viewBox="0 0 24 24"><path d="M12 20.8S3.4 15.5 3.4 9.8A4.6 4.6 0 0 1 12 7.3a4.6 4.6 0 0 1 8.6 2.5c0 5.7-8.6 11-8.6 11z"/></symbol></svg>
<div class="wayra-rating" data-size="md" data-readonly="true" data-color="primary"><div class="wayra-rating-items" role="img" aria-label="3.5 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:50"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>
<div class="wayra-rating" data-size="md" data-readonly="true" data-color="secondary"><div class="wayra-rating-items" role="img" aria-label="3.5 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:50"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>
<div class="wayra-rating" data-size="md" data-readonly="true" data-color="accent"><div class="wayra-rating-items" role="img" aria-label="3.5 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:50"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>
<div class="wayra-rating" data-size="md" data-readonly="true" data-color="neutral"><div class="wayra-rating-items" role="img" aria-label="3.5 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:50"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>
<div class="wayra-rating" data-size="md" data-readonly="true" data-color="info"><div class="wayra-rating-items" role="img" aria-label="3.5 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:50"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>
<div class="wayra-rating" data-size="md" data-readonly="true" data-color="success"><div class="wayra-rating-items" role="img" aria-label="3.5 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:50"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>
<div class="wayra-rating" data-size="md" data-readonly="true" data-color="warning"><div class="wayra-rating-items" role="img" aria-label="3.5 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:50"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>
<div class="wayra-rating" data-size="md" data-readonly="true" data-color="error"><div class="wayra-rating-items" role="img" aria-label="3.5 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:50"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>

The default is warning, because a rating is warm. That is only possible because the shipped warning is a bronze rather than an amber: every role has to clear 4.5:1 against base-100 before it may carry a component, and an amber does not.

A rating reads exactly one token — the accent, on the filled layer. The empty layer is a fixed mix of base-content, the same in every role, so nothing has to be measured twice. There is no -content job here, because nothing sits on top of an icon.

The icon walks the --wayra-size-selector ladder on the same multipliers Checkbox and Radio walk, so a star and a checkbox at the same step are the same size side by side.

Code
<Rating readOnly value={4} size="xs" />
<Rating readOnly value={4} size="sm" />
<Rating readOnly value={4} size="md" />
<Rating readOnly value={4} size="lg" />
<Rating readOnly value={4} size="xl" />
<svg style="display:none" aria-hidden="true"><symbol id="hu-star" viewBox="0 0 24 24"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></symbol><symbol id="hu-heart" viewBox="0 0 24 24"><path d="M12 20.8S3.4 15.5 3.4 9.8A4.6 4.6 0 0 1 12 7.3a4.6 4.6 0 0 1 8.6 2.5c0 5.7-8.6 11-8.6 11z"/></symbol></svg>
<div class="wayra-rating" data-color="warning" data-readonly="true" data-size="xs"><div class="wayra-rating-items" role="img" aria-label="4 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>
<div class="wayra-rating" data-color="warning" data-readonly="true" data-size="sm"><div class="wayra-rating-items" role="img" aria-label="4 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>
<div class="wayra-rating" data-color="warning" data-readonly="true" data-size="md"><div class="wayra-rating-items" role="img" aria-label="4 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>
<div class="wayra-rating" data-color="warning" data-readonly="true" data-size="lg"><div class="wayra-rating-items" role="img" aria-label="4 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>
<div class="wayra-rating" data-color="warning" data-readonly="true" data-size="xl"><div class="wayra-rating-items" role="img" aria-label="4 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><use href="#hu-star"/></svg></span></span></div></div>

The cell around the icon does not follow it all the way down:

Size Icon Editable cell Half-mode hit (half the cell)
xs 16px 24 × 24px 12 × 24px
sm 18px 24 × 24px 12 × 24px
md 20px 24 × 24px 12 × 24px
lg 24px 24 × 24px 12 × 24px
xl 28px 28 × 28px 14 × 28px

Every whole-step hit clears 24 × 24 CSS px; half mode trades that on the halves for identical geometry (see Half steps). A read-only row has no targets, so its cell is exactly the icon. The floor is a knob — --wayra-rating-target: 24px — if a dense table really needs to go under it.

star and heart ship. Anything else is a ReactElement you hand over, and it is rendered into both layers exactly as the built-ins are.

Code
<Rating readOnly value={3.5} />
<Rating readOnly value={3.5} icon="heart" color="error" />
<Rating
readOnly
value={3.5}
icon={<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">…</svg>}
/>
<div class="wayra-rating" data-color="warning" data-size="md" data-readonly="true"><div class="wayra-rating-items" role="img" aria-label="3.5 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:50"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span></span></div></div>
<div class="wayra-rating" data-size="md" data-readonly="true" data-color="error"><div class="wayra-rating-items" role="img" aria-label="3.5 of 5"><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 20.8S3.4 15.5 3.4 9.8A4.6 4.6 0 0 1 12 7.3a4.6 4.6 0 0 1 8.6 2.5c0 5.7-8.6 11-8.6 11z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 20.8S3.4 15.5 3.4 9.8A4.6 4.6 0 0 1 12 7.3a4.6 4.6 0 0 1 8.6 2.5c0 5.7-8.6 11-8.6 11z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 20.8S3.4 15.5 3.4 9.8A4.6 4.6 0 0 1 12 7.3a4.6 4.6 0 0 1 8.6 2.5c0 5.7-8.6 11-8.6 11z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 20.8S3.4 15.5 3.4 9.8A4.6 4.6 0 0 1 12 7.3a4.6 4.6 0 0 1 8.6 2.5c0 5.7-8.6 11-8.6 11z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 20.8S3.4 15.5 3.4 9.8A4.6 4.6 0 0 1 12 7.3a4.6 4.6 0 0 1 8.6 2.5c0 5.7-8.6 11-8.6 11z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 20.8S3.4 15.5 3.4 9.8A4.6 4.6 0 0 1 12 7.3a4.6 4.6 0 0 1 8.6 2.5c0 5.7-8.6 11-8.6 11z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:50"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 20.8S3.4 15.5 3.4 9.8A4.6 4.6 0 0 1 12 7.3a4.6 4.6 0 0 1 8.6 2.5c0 5.7-8.6 11-8.6 11z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 20.8S3.4 15.5 3.4 9.8A4.6 4.6 0 0 1 12 7.3a4.6 4.6 0 0 1 8.6 2.5c0 5.7-8.6 11-8.6 11z"/></svg></span></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 20.8S3.4 15.5 3.4 9.8A4.6 4.6 0 0 1 12 7.3a4.6 4.6 0 0 1 8.6 2.5c0 5.7-8.6 11-8.6 11z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 20.8S3.4 15.5 3.4 9.8A4.6 4.6 0 0 1 12 7.3a4.6 4.6 0 0 1 8.6 2.5c0 5.7-8.6 11-8.6 11z"/></svg></span></span></div></div>

name, disabled and ref are the native <fieldset> props, so a plain form works with no adapter: FormData posts one entry under the shared name, carrying the chosen option’s value.

react-hook-form is built in, the same way it is on Radio. Inside Form, every option registers itself on name alone; react-hook-form de-duplicates by name and collects the options’ refs, so one prop wires the whole group. An explicit disabled wins over the freeze a submitting form applies, and noRhf turns all of it off.

const schema = z.object({
score: z.coerce.number().min(1, 'Pick a score.'),
});
const form = useForm({
// A string, because a native radio's value is a string.
defaultValues: { score: '' },
resolver: zodResolver(schema),
});
<Form form={form} onSubmit={save}>
<Rating name="score" label="How did we do?" />
<Button type="submit">Save</Button>
</Form>

This is the one thing to remember. The score comes off a native radio’s value attribute, so what lands in the store is "4", "2.5", or "" for no rating — never a number.

  • Coerce it in the schema. z.coerce.number() with zod, Number(value) anywhere else.
  • Seed defaultValues with a string. { score: '' } for none, { score: '3' } to start at three. A numeric 3 matches no radio, so the row would render empty.
  • onChange is the exception and gives you a number — 4, 2.5, 0 — because it is the component’s own prop rather than the DOM’s.

Rating owns its <fieldset>, which is the difference from Radio: a radio has no group element of its own, so its form error can only mark the options and the message has to be your markup. Here the message has exactly one place to go — the hint under the row, role="alert", pointed at by aria-describedby on the fieldset — and it is announced once for the whole group. aria-invalid sits on the fieldset too, which is why it carries role="radiogroup": ARIA 1.2 supports the state on a radio group and deprecates it on the plain group a fieldset would otherwise be.

Rate the driver

Pick a score first.

Code
<Rating name="score" label="Rate the driver" error="Pick a score first." />
<fieldset role="radiogroup" class="wayra-rating" data-color="warning" data-size="md" aria-invalid="true"><legend class="wayra-rating-legend">Rate the driver</legend><div class="wayra-rating-items"><input class="wayra-rating-clear" type="radio" name="error-demo" value="" aria-label="No rating" checked /><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="error-demo" value="1" aria-label="1 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="error-demo" value="2" aria-label="2 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="error-demo" value="3" aria-label="3 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="error-demo" value="4" aria-label="4 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="error-demo" value="5" aria-label="5 of 5" /></label></span></div><p class="wayra-rating-hint">Pick a score first.</p></fieldset>

An explicit error prop still wins over the form’s, and an explicit aria-invalid — including false — wins over both.

Outside a Form the component works either way:

<Rating defaultValue={3} onChange={setScore} /> {/* it keeps the score */}
<Rating value={score} onChange={setScore} /> {/* you keep it */}

Choose name or value. Inside a Form, on a field with a name, the store is the value: a value prop is ignored and the console says so once. It never paints the row and never seeds the store, not even while the store is empty, so what the row shows is always what the form posts. To control a form field, call form.setValue("score", …); outside a Form, or with noRhf, value controls the row as usual. A read-only row registers nothing, so its value always paints.

reset() and setValue() repaint the row, because the painted fill reads the store rather than a mirror of it. With no defaultValues a bare reset() leaves the store empty, and the row goes back to defaultValue (or to no rating): that is the score painted, the radio checked, and what the next submit posts.

Rate this order
Code
<Rating name="score" label="Rate this order" defaultValue={3} disabled />
<fieldset role="radiogroup" class="wayra-rating" data-color="warning" data-size="md" disabled><legend class="wayra-rating-legend">Rate this order</legend><div class="wayra-rating-items"><input class="wayra-rating-clear" type="radio" name="disabled-demo" value="" aria-label="No rating" /><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="disabled-demo" value="1" aria-label="1 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="disabled-demo" value="2" aria-label="2 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:100"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="disabled-demo" value="3" aria-label="3 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="disabled-demo" value="4" aria-label="4 of 5" /></label></span><span class="wayra-rating-item" style="--wayra-rating-fill:0"><span class="wayra-rating-icon" data-role="base"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><span class="wayra-rating-icon" data-role="fill"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.86 6.05 6.54.92-4.74 4.72 1.13 6.6L12 17.83 6.21 20.9l1.13-6.6L2.6 9.57l6.54-.92z"/></svg></span><label class="wayra-rating-hit"><input class="wayra-rating-input" type="radio" name="disabled-demo" value="5" aria-label="5 of 5" /></label></span></div></fieldset>

One attribute on the <fieldset> takes every option out at once, which is the practical reason the group is a fieldset and not a <div> of radios: there is no list of inputs to keep in sync. The row also stops answering the pointer, so no hover preview fires behind the fade.

There is one way to draw a rating in v1: a solid empty silhouette under a solid filled one. The prototype this was ported from also had an outline variant — hollow empty icons with a stroked edge — and it may come back as data-variant="outline" if there is a second consumer. Until then, one axis fewer.

4.3 (128) next to the icons is two spans and a number you already have. Building it in would mean a number formatter, a locale, a review-count prop and a way to hide all three.

For the same reason Radio has none: a shared name inside a <fieldset> already is a group, and the arrow keys that come with it are the ones a rating needs.

RatingProps extends ComponentProps<"fieldset"> minus color, defaultValue, onChange and ref.

Prop Type Default Description
color WayraColor "warning" The filled layer’s color.
size "xs" | "sm" | "md" | "lg" | "xl" "md" Off --wayra-size-selector, same steps as Checkbox.
count number 5 How many icons. Clamped to at least 1, whole icons only.
half boolean false Two hits per icon; editable mode only.
clearable boolean true The “No rating” option and click-to-clear.
readOnly boolean false Show a score instead of asking for one.
precision "exact" | "half" | "full" "exact" Read-only rounding.
icon "star" | "heart" | ReactElement "star" Must paint with currentColor.
value number — Controlled score. Ignored inside a Form on a named field.
defaultValue number — Uncontrolled seed.
onChange (value: number) => void — The chosen score as a number, 0 once cleared.
formatLabel (value: number, count: number) => string `${value} of ${count}` Names each option, and the read-only row.
label ReactNode — The <legend>, or a <span> while read-only.
helper ReactNode — Group hint under the row.
error ReactNode — Takes the hint slot, adds role="alert", sets aria-invalid.
noRhf boolean false Opt out of the surrounding Form.
unstyled boolean false Drops the classes, keeps the data attributes.

name, disabled, id and ref are the native fieldset props, typed. ref lands on the root, which is the fieldset while editable and a <div> once readOnly, so it is typed Ref<HTMLElement>.

The class and data-* pair is public API and works on hand-authored markup:

<fieldset role="radiogroup" class="wayra-rating" data-color="warning" data-size="md" data-half="true">
<legend class="wayra-rating-legend">Rate this order</legend>
<div class="wayra-rating-items">
<input class="wayra-rating-clear" type="radio" name="score" value="" aria-label="No rating" checked />
<span class="wayra-rating-item" style="--wayra-rating-fill:100">
<span class="wayra-rating-icon" data-role="base"><svg …></svg></span>
<span class="wayra-rating-icon" data-role="fill"><svg …></svg></span>
<label class="wayra-rating-hit" data-half="start">
<input class="wayra-rating-input" type="radio" name="score" value="0.5" aria-label="0.5 of 5" />
</label>
<label class="wayra-rating-hit" data-half="end">…</label>
</span>
…
</div>
<p class="wayra-rating-hint">Optional.</p>
</fieldset>

The public knobs are --wayra-rating-size, --wayra-rating-target, --wayra-rating-gap, --wayra-rating-tone, --wayra-rating-empty, --wayra-rating-duration and the per-item --wayra-rating-fill.

  • Grouping is the <fieldset> with role="radiogroup", named by its <legend> — or by an aria-label when the rating renders bare. Every option is named by formatLabel.
  • Focus uses :focus-visible and rings the icon the keyboard is on, not the row, because the arrow keys move the selection through the group. The hidden “No rating” option has no icon of its own, so the row rings for it — a focused control with no visible indicator is exactly what that rule exists to prevent.
  • Targets clear 24 × 24 CSS px in every size (WCAG 2.5.8). In half mode the two halves split that cell and go under the floor on purpose — the star stays a full-size target, and the arrow keys are the precise path (see Half steps).
  • Windows high contrast repaints the filled layer as Highlight and the empty one as CanvasText, or both as GrayText when disabled, with forced-color-adjust: none so the two stay apart.
  • Reduced motion zeroes the sweep, the fill transition and the commit pop. The rating still fills; it just arrives instantly. A press no longer gives: the pressed star keeps the size the pointer gave it.
  • Right to left is handled down to the browser floor: clip-path: inset() has no logical form, so a dir="rtl" on the rating or on any ancestor makes the fill eat the other edge and grow from the right. :dir() then puts an LTR island inside RTL back where the engine has it (Chrome 120 / Safari 16.4); below that, such an island fills from the wrong side.

Do

Average 4.3 (128 reviews)

Use readOnly for an average, and put the number beside it. Five icons cannot say "4.3".

Don't

Average 4.3

Show an average with a live radio group. It rounds the value to a whole score and invites a click that changes nothing.

Do

How was the delivery?

Ship the row empty and let the user say nothing. "No rating" is checked, so Tab has somewhere to land.

Don't

How was the delivery?

Pre-select a score. Every submission is then at least a 3, whether the user looked at the row or not.

Do

Five

Keep count at five. It is the shape everyone reads without counting.

Don't

Ten

Ask for a ten-icon score. The user counts icons instead of judging, and the row stops fitting on a phone.