Rating
Collect or display a rating with stars or custom icons, including half steps.
import { Rating } from '@youj/react'
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use it to ask for a quick judgment on a fixed scale, such as a review, a satisfaction survey or a score, and to show one back in a list of products or results. It's one focusable control, so a keyboard user reaches it with one Tab and sets it with the arrows or a digit. When the scale has named steps that matter more than their position, such as "Not likely" to "Very likely", a Radio Group says so more plainly. For an arbitrary number along a range, use a Slider.
import { useState } from 'react'
import { Rating } from '@youj/react'
export function RateStay() {
const [rating, setRating] = useState(0)
return <Rating aria-label="Overall" value={rating} onValueChange={setRating} allowHalf />
}The value is a number from 0 to max, where 0 means no rating. Each icon sits in a small key of its own, in a row like a segmented control's segments. An empty key is a plain key with a muted outline; a filled one turns the accent, with the icon solid in the accent's contrast color, and a half step fills the left half of a key. Hovering previews the value under the pointer: the keys fill there one by one, the key under the pointer lifts, and keys of the value already set keep a faint accent tint while the preview sits below them. Leaving restores it. Clicking commits it, and clicking the current value again clears it. allowHalf rates half a key at a time. With a name, the value is submitted with a form.
A readout
Rating draws no text of its own beyond what it says to a screen reader. onPreviewChange reports the value under the pointer, or null when it leaves, so a readout beside it can say what a click would set, as the demo's "4.5 of 5 · Excellent" does. Hide that readout from assistive tech (aria-hidden): the slider already speaks its value.
const [rating, setRating] = useState(0)
const [preview, setPreview] = useState<number | null>(null)
const shown = preview ?? rating
<Rating aria-label="Overall" value={rating} onValueChange={setRating} onPreviewChange={setPreview} />
<span aria-hidden>{shown ? `${shown} of 5` : 'Tap to rate'}</span>Description
A review card with a half-step Rating and a readout that names what a click would set.
Read-only ratings for lists and summaries.
readOnly shows a rating without interaction, for lists and summaries. It's an image with a label such as "Rated 4.5 out of 5", so a screen reader hears one sentence and not five icons.
Custom icons, scales, sizes and states.
The default star is Rating's own: plump points, rounded by the same stroke that draws its outline. icon takes a Youj icon such as HeartIcon, or a lucide-style one, filled with the current color, and unit names it in the value text ("4 of 5 hearts"). A filled key is the accent (its text color); tint it by setting the color of [data-slot=rating-fill], as the hearts do here with text-danger. max sets the number of keys, 5 by default, and size is sm, default or lg (24, 28 and 32px keys). In a right-to-left layout the fill runs from the right and the arrow keys follow it.
Code
rating-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsvalue, defaultValue, onValueChange… (16)
Props
| Prop | Type | |
|---|---|---|
value | number | – |
defaultValue | numberdefault 0 | 0 |
onValueChange | (value: number) => void | – |
onPreviewChange | (value: number | null) => voidThe value under the pointer while it hovers, or null when it leaves: what a click would set. | – |
max | numberNumber of icons. default 5 | 5 |
allowHalf | booleanHalf steps. default false | false |
readOnly | booleandefault false | false |
disabled | booleandefault false | false |
icon | RatingIconThe icon, filled with currentColor: a Youj icon or a lucide-style one. default Star | Star |
unit | stringWhat one icon is, for the value text ("stars", "hearts"). default 'stars' | 'stars' |
size | 'sm' | 'default' | 'lg'default 'default' | 'default' |
name | stringSubmits the value in a hidden input. | – |
className | string | – |
id | string | – |
aria-label | string | – |
aria-labelledby | string | – |
aria-describedby | string | – |
MotionPreview sweep, lift, commit press and glint · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Preview sweepeach key’s fill slides to its share, one tight stagger step per key from the edge it moves from | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) | |
| Liftthe key under the pointer rises 2px and scales to 1.05 | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) | |
| Commit pressthe committed key presses to 0.86 and springs back with a little overshoot | spring.settle0.45s, bounce 0.3 | 0.45s, bounce 0.3 | |
| Ripplethe filled keys before it rise to 1.06 and back, a tight stagger step apart | duration.baseease.emphasized220ms · cubic-bezier(0.6, 0, 0.1, 1) | 220mscubic-bezier(0.6, 0, 0.1, 1) | |
| Glintfive short accent rays open just outside the committed key and fade | duration.slowease.enter340ms · cubic-bezier(0.16, 1, 0.3, 1) | 340mscubic-bezier(0.16, 1, 0.3, 1) |
Reduced motion
The fill snaps to the new value; nothing lifts, presses or glints.
Reduced motion
The fill moves to the new value at once, and nothing lifts, presses or glints. The hover preview still shows what a click will commit, with the keys of the value already set faintly tinted.
AccessibilityKeyboard, ARIA, focus
Accessibility
- It's one
sliderwith a value text such as "3.5 of 5 stars", or "No rating" at 0. Name it witharia-labeloraria-labelledby, as the demo does with the Field's label. - ArrowRight and ArrowUp step up, ArrowLeft and ArrowDown step down (mirrored in right-to-left layouts), Home and Backspace clear it, End sets the maximum and a digit jumps to that value.
- The hit area reaches a few pixels past the keys, and the focus ring hugs the row.
- Disabled, it leaves the tab order and reports
aria-disabled. readOnlyrendersrole="img"with a label ("Rated 4.5 out of 5", or youraria-label), and takes no focus.- Empty keys are edged in the control line tone, which holds 3:1 against the surface, so the scale reads before anything is filled. A filled key's icon is the accent's contrast color on the accent.