Skip to content

Rating

Collect or display a rating with stars or custom icons, including half steps.

import { Rating } from '@youj/react'

Installation & usage

npm i @youj/react

For 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>

Reference

Propsvalue, defaultValue, onValueChange… (16)

Props

Rating props
PropType
valuenumber–
defaultValuenumber

default 0

0
onValueChange(value: number) => void–
onPreviewChange(value: number | null) => void

The value under the pointer while it hovers, or null when it leaves: what a click would set.

–
maxnumber

Number of icons.

default 5

5
allowHalfboolean

Half steps.

default false

false
readOnlyboolean

default false

false
disabledboolean

default false

false
iconRatingIcon

The icon, filled with currentColor: a Youj icon or a lucide-style one.

default Star

Star
unitstring

What one icon is, for the value text ("stars", "hearts").

default 'stars'

'stars'
size'sm' | 'default' | 'lg'

default 'default'

'default'
namestring

Submits the value in a hidden input.

–
classNamestring–
idstring–
aria-labelstring–
aria-labelledbystring–
aria-describedbystring–
MotionPreview sweep, lift, commit press and glint · reduced motion

Motion

Motion tokens
MomentTokens
Preview sweepeach key’s fill slides to its share, one tight stagger step per key from the edge it moves from
duration.fastease.standard

140ms · 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.standard

140ms · 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.settle

0.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.emphasized

220ms · 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.enter

340ms · 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 slider with a value text such as "3.5 of 5 stars", or "No rating" at 0. Name it with aria-label or aria-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.
  • readOnly renders role="img" with a label ("Rated 4.5 out of 5", or your aria-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.