Skip to content

Rating

A star/score control for collecting or displaying a rating.

Stablev0.4.2added in v0.2.0@garn/ui/rating
On this page

Default

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.

terminal
npx garn-ui add rating
First time? Set up garn in your project
Registry
rating
Deps
class-variance-authoritylucide-react
Registry deps
utilsuse-controllable-state

Import

import { Rating } from "@garn/ui/rating";

Props

The component's public props and their 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
Default
true
defaultValue
Description
Uncontrolled initial rating.
Type
number
Default
0
disabled
Description
Keep the radios but make them inert — stays in the form/accessibility tree, greyed out.
Type
boolean
Default
false
emptyIcon
Description
Symbol shown for empty positions; falls back to `icon` when omitted.
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
Default
5
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
Default
1
readOnly
Description
Render-only: no inputs, `role="img"`, fractional fill for averages.
Type
boolean
Default
false
required
Description
Form validation — a value must be picked; implies not clearable.
Type
boolean
Default
false
value
Description
Controlled rating (0…max, in `precision` steps).
Type
number

Plus 277 inherited native HTML attributes.

Styling

Target these data-slots and states, and remap these tokens, to restyle without forking the component.

[data-slot="rating"]

States

SelectorState
:focus-visiblefocus-visible
:hoverhover-preview
[data-disabled]disabled
[data-readonly]read-only
PropertyTokenTier
fill--garn-badge-warning-boldbrandablesemantic
ring--garn-ringbrandablesemantic
symbolSize--garn-box-lgsemantic · identity

Private instance vars (never externalize): --r-fill--r-size

When to use

Collect or display a score on a small fixed scale — a star review, a helpfulness vote, a difficulty rating — where cumulative symbols read faster than a number.

Reach for something else when

  • A precise value on a large scale (use a number input)
  • an approximate position on a continuum (use slider)
  • a single on/off reaction (use toggle or switch).

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

Do
  • 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 required to force a choice in a form.
  • Show a count or word-scale label alongside, driven by onHoverChange.
Don't
  • 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).

Troubleshooting

Type error or nothing selected when passing an array to value.

Cause. Rating's value is a single number, not an array like Slider.

Fix. Pass a number — value={3} / defaultValue={2.5}.

Console warns that the rating has no accessible name.

Cause. An interactive rating is a radiogroup and needs a name.

Fix. Add aria-label or aria-labelledby (read-only mode names itself from the score).

A fractional value like 4.3 renders as a whole/half star only.

Cause. Fractional fill is a read-only display concern; interactive selection snaps to precision.

Fix. Use readOnly for averages (any decimal fills exactly); precision={0.5} only affects what a user can pick.

required never blocks form submission.

Cause. The default clear anchor keeps a value (0) checked, satisfying required.

Fix. Set required — it drops the clear anchor so an unrated field is invalid (and implies not-clearable).

Accessibility

Role
radiogroup
ARIA APG
radiogroup
Focus
A focused radio rings its whole symbol.

Accessibility requirements

warn

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

ArrowRightArrowUpSelect the next value (native radio behaviour).
ArrowLeftArrowDownSelect the previous value.
HomeJump to the lowest value.
EndJump to `max`.
PageUpPageDownNudge by one whole symbol.
A2.1.1KeyboardAA2.4.7Focus VisibleA4.1.2Name, Role, Value
  • 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").