ComponentsForm
Rating
Lets people give a score out of a row of stars, or shows a score that cannot change.
Import
import { Rating } from "@booleanpress/ui/rating"Usage
Each star is a radio of one group, so a screen reader hears "3 of 5, radio button, checked", and the arrow keys move along the stars.
import { Rating } from "@booleanpress/ui/rating"
export function RateReply() {
return <Rating defaultValue={3} aria-label="Rate this reply" />
}The value is a number from 0 (no rating) to max (5 by default). It is uncontrolled with defaultValue, or controlled with value and onValueChange. Choosing the current rating again (a click, or Space or Enter on the checked star) clears it to 0, as do Backspace and Delete, and a polite message says so (the provider string ratingCleared); allowClear={false} keeps a rating once one is chosen. allowHalf splits each star into two radios, its start half and its whole, so the value moves in halves. readOnly shows a score without letting it change: the stars become one image named with the value, out of the tab order. icon and emptyIcon replace the filled and outlined star, for hearts or any other mark. size is sm (14 px stars), default (16 px) or lg (20 px) and follows the provider's controlSize. Name the rating with aria-label or aria-labelledby. Each star's name is the provider string ratingValue, "{value} of {max}", with the numbers in the provider's locale.
Examples
Basic
Five stars, three chosen; the star under the pointer darkens, and the chosen star pressed again clears the rating.
Half stars
allowHalf lets people choose the start half of a star, here 3.5.
Controlled
value and onValueChange, set from buttons too, with large stars.
import { useState } from "react"
import { Button } from "@booleanpress/ui/button"
import { Rating } from "@booleanpress/ui/rating"
export default function RatingControlled() {
const [score, setScore] = useState(4)
return (
<div className="flex flex-col items-center gap-4">
<Rating value={score} onValueChange={setScore} allowHalf aria-label="Ticket satisfaction" size="lg" />
<div className="flex gap-2">
{[2.5, 3, 3.5].map((preset) => (
<Button key={preset} size="sm" variant="outline" onClick={() => setScore(preset)}>
{preset} stars
</Button>
))}
</div>
</div>
)
}Number of stars
max={10} draws ten stars; each is named out of 10.
Custom icon
icon and emptyIcon draw filled and outlined hearts in place of stars.
Read only
readOnly shows an average of 4.5 as one image named "Average rating 4.5 of 5", outside the tab order.
import { Rating } from "@booleanpress/ui/rating"
export default function RatingReadOnly() {
return (
<div className="flex items-center gap-2">
<Rating readOnly allowHalf value={4.5} aria-label="Average rating" />
<span className="text-sm/normal text-muted-foreground">from 128 reviews</span>
</div>
)
}Disabled
disabled dims the stars and takes them out of the tab order.
Sizes
sm draws 14 px stars, default 16 px and lg 20 px.
import { Rating } from "@booleanpress/ui/rating"
export default function RatingSizes() {
return (
<div className="flex flex-col items-center gap-4">
<Rating size="sm" defaultValue={3} aria-label="Small" />
<Rating defaultValue={3} aria-label="Default" />
<Rating size="lg" defaultValue={3} aria-label="Large" />
</div>
)
}Accessibility
- Semantics
- A
role="radiogroup"ofrole="radio"buttons, one per star (two per star withallowHalf), each witharia-checked. Read only, it is a singlerole="img"named with the value; the stars inside are hidden from screen readers. - Labels
- Name the group with
aria-labeloraria-labelledby. Each star is named "3 of 5" from the provider stringratingValue; a half is "3.5 of 5". The read-only image is named with youraria-labeland the value. - Focus
- The group is one tab stop: Tab lands on the chosen star, or the first star when none is chosen. The star with keyboard focus has a 1px
--ringoutline 2px round it. - Known limits
- Clearing is not part of the radio group pattern: a screen reader hears the star become unchecked, and the
ratingClearedmessage, but nothing tells people beforehand that a second press clears. Say so near the rating where it matters. - Stars are 16 px. Keep them at
lgor larger where people tap with a finger, so each meets WCAG 2.5.8's 24 px with its gap. - Half stars split a 16 px star into two 8 px targets; they suit a pointer better than a finger.
- Clearing is not part of the radio group pattern: a screen reader hears the star become unchecked, and the
Keyboard
| Key | Behaviour |
|---|---|
| Tab | Moves to the chosen star, or to the first star when none is chosen, then out of the rating. |
| โorโ | Chooses the next star (half star with allowHalf), from the last back to the first; โ goes the other way in right-to-left pages. |
| โorโ | Chooses the previous star, from the first round to the last; โ goes the other way in right-to-left pages. |
| Space | Chooses the focused star when it is not chosen yet; on the chosen star, clears the rating to 0 (unless allowClear={false}). |
| Enter | Chooses the focused star; on the chosen star, clears the rating to 0 (unless allowClear={false}). |
| BackspaceorDelete | Clears the rating to 0 and announces it (unless allowClear={false}). |
API
Rating
Renders a div and passes it every other prop.
| Prop | Type | Default | Description |
|---|---|---|---|
allowClear | boolean | true | Choosing the current rating again (a click, or Space or Enter on the checked star) clears it to 0, as do Backspace and
Delete. true by default; false keeps a rating once one is chosen. |
allowHalf | boolean | false | Lets people choose half stars: each star takes two radios, its start half and its whole. |
defaultValue | number | 0 | The rating at the start, when it controls itself. 0 by default. |
dir | "ltr" | "rtl" | ||
disabled | boolean | Dims the stars and ignores the pointer and the keyboard. | |
emptyIcon | ReactNode | The mark of an unchosen star, in place of the outlined star. Defaults to icon, else the outlined star. | |
form | string | ||
icon | ReactNode | The mark of a chosen star, in place of the filled star. | |
max | number | 5 | The number of stars. 5 by default. |
name | string | The form field name; the rating is submitted with the form. | |
onValueChange | ((value: number) => void) | Called with the new rating when a star is chosen. | |
orientation | "horizontal" | "vertical" | horizontal | vertical stacks the stars. horizontal by default. |
readOnly | boolean | false | Shows the rating without letting it change: an image named with the value, not a radio group. |
required | boolean | The form cannot be submitted until a star is chosen. | |
size | "default" | "sm" | "lg" | Stars of 14, 16 or 20 px. Defaults to the provider's controlSize. | |
value | number | The rating, when you control it: 0 (none) to max, in halves with allowHalf. |
Every part takes className, merged with its defaults by cn(), and ref, which reaches the element it renders.
Data attributes: data-slot="rating-item" (Rating), and data-state, data-size, data-orientation, data-allow-clear.
Provider strings: ratingValue, ratingCleared (BooleanUIProvider's strings).
Theming
Unchosen stars are --muted-foreground, chosen ones and the star under the pointer --primary; focus is a 1px --ring outline. A disabled rating is drawn at 60% opacity.
The tokens its classes read. Change their values in your theme, and it follows.
| Token | Used for |
|---|---|
--muted-foreground | text |
--primary | text |
--ring | outline |