Rating
A star/score control for collecting or displaying a rating.
@garn/ui/ratingDefault
A five-star rating with an accessible name.
Required
Required in a form — no clear anchor, native validation, submits its value.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add rating- Registry
rating- Source
- rating.tsx
- Deps
class-variance-authoritylucide-react- Registry deps
utilsuse-controllable-state
Import
import { Rating } from "@garn/ui/rating";Props
Generated from the component's TypeScript types.
size- Description
- No description.
- Type
"lg" | "md" | "sm"- Default
md
tone- Description
- No description.
- Type
"brand" | "danger" | "discovery" | "info" | "neutral" | "success" | "warning"- Default
warning
clearable- Description
- Re-selecting the current value resets it to 0 (ignored when `required`).
- Type
boolean
defaultValue- Description
- Uncontrolled initial rating.
- Type
number
disabled- Description
- No description.
- Type
boolean
emptyIcon- Description
- No description.
- Type
React.ReactNode
getValueText- Description
- Spoken label for a value — defaults to `"{value} of {max}"`.
- Type
(value: number, max: number) => string
icon- Description
- Filled symbol; also the empty symbol unless `emptyIcon` is given.
- Type
React.ReactNode
max- Description
- Number of symbols.
- Type
number
name- Description
- Groups the radios and names the form field.
- Type
string
onHoverChange- Description
- Fires with the previewed value on hover, `null` on leave.
- Type
(value: number | null) => void
onValueChange- Description
- Fires with the committed rating; `0` when cleared.
- Type
(value: number) => void
precision- Description
- Selection granularity — whole or half symbols.
- Type
0.5 | 1
readOnly- Description
- Render-only: no inputs, `role="img"`, fractional fill for averages.
- Type
boolean
required- Description
- Form validation — a value must be picked; implies not clearable.
- Type
boolean
value- Description
- Controlled rating (0…max, in `precision` steps).
- Type
number
Plus 277 inherited native HTML attributes.
Styling
Target these data-slots and remap these tokens to restyle without forking the component.
[data-slot="rating"]| Property | Token | Tier |
|---|---|---|
fill | --garn-badge-warning-boldbrandable | semantic |
ring | --garn-ringbrandable | semantic |
symbolSize | --garn-box-lg | semantic · identity |
Private instance vars (never externalize): --r-fill--r-size
Overview
Rating collects or shows a score on a small fixed scale — the five-star review, the thumbs-worth of a helpfulness vote, a difficulty out of ten. It is one component in two modes: interactively it's a radiogroup of real (hidden) radios, so keyboard, focus and form submission are native; read-only it's an image that announces a fractional average and clips the fill to any decimal. Symbols fill cumulatively up to the value, with a live hover-preview that reverts on leave. When the scale is large or the exact number matters, a number field or slider is clearer; for a single yes/no reaction, use a Toggle.
Guidelines
Name the control. An interactive rating is a radiogroup and needs an accessible name — pass aria-label ("Rate this product") or aria-labelledby. It warns in dev when unnamed.
Pick the right mode. Collecting input → interactive (keep it clearable so people can un-rate, or set required in a form to force a choice). Showing an aggregate like 4.3★ → readOnly with precision={0.5}; read-only clips the fill to the exact decimal and announces "4.3 of 5".
Surface the meaning. Pair the stars with a count or a word scale ("Terrible…Excellent") driven by onHoverChange, and keep max small (5 is the convention) — a 10- or 100-point scale is better as a number field.
Tone is intent, not decoration. Amber (warning) is the default star; use another tone only when it carries meaning (a red heart for favourites). Empty symbols stay neutral grey by design.
Best practices
- Give the interactive rating an accessible name (aria-label / aria-labelledby).
- Use
readOnly+precision={0.5}to display fractional averages like 4.3. - Keep it clearable for optional input, or
requiredto force a choice in a form. - Show a count or word-scale label alongside, driven by
onHoverChange.
- Don't leave an interactive rating unlabeled.
- Don't use a rating for a large or precise scale — reach for a number input.
- Don't recolour the symbols for decoration; tone should carry meaning.
Content guidelines
- Name what's being rated in the label ("Rate this product", not "Rating").
- Show the numeric score and review count next to read-only averages.
- Use a consistent word scale if you label hover values (Terrible → Excellent).
Accessibility
- Role
radiogroup- ARIA APG
- radiogroup
- Focus
- A focused radio rings its whole symbol.
Enforceable contracts
Give the interactive rating an accessible name (aria-label or aria-labelledby). Warns in dev.
when an interactive Rating has no aria-label / aria-labelledby
Keyboard
| ArrowRightArrowUp | Select the next value (native radio behaviour). |
| ArrowLeftArrowDown | Select the previous value. |
| Home | Jump to the lowest value. |
| End | Jump to `max`. |
| PageUpPageDown | Nudge by one whole symbol. |
- • Interactive: role=radiogroup built from real (visually-hidden) radios — give it an accessible name via aria-label or aria-labelledby.
- • Each radio is named for its value (e.g. "3 of 5"); a 'No rating' radio anchors the empty state (omitted when `required`).
- • Read-only: role=img with an aria-label announcing the score (e.g. "4.3 of 5").
- Status
- Stable
- Version
v0.3.0- Introduced
v0.2.0
garn doesn't yet record a per-release version history. This reflects the component's current lifecycle state; full changelog entries will appear once the metadata pipeline emits a lifecycle.history field.