DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
CheckboxRadioSwitchTextareaNumberFieldOTP InputSliderCircularSliderRatingSelectPickerComboboxSearchSearchSuggestTokenizerSegmentedControl
AI agentsContributing
  1. Components
  2. Rating

Rating

Captures a score in four shapes — star scale, numeric NPS scale, emoji reaction picker and binary thumbs — as a keyboard-operable form control, or as a read-only display that paints an exact fractional average.

$ pnpm add @tessinaui/ui

When to use

Rating is a form control: it holds a value, it can be submitted, and it is operable from the keyboard.

  • A score the person picks and you submit. Name the group with label, and add name to submit the value with the form.
  • A quick sentiment or satisfaction answer. Pick the variant that matches the question: star for a review score, numeric for NPS or CSAT, reaction for a feeling, binary for "was this helpful?".
  • A score you are only displaying. Set readOnly: it becomes a single labelled image rather than a set of unusable radios, and paints an exact fractional average.
  • A fixed, short scale. Every variant has a bounded set of steps. Keep the scale the same wherever the score appears so results compare.
  • A visible name for the question. Say what is being rated, and pair the score with a readout (showValue) or end labels (lowLabel, highLabel) when the scale needs anchoring.

When not to use

  • For thumbs in a chat or AI message toolbar. Those act immediately, hold no value, and belong in the message's action row: use IconButton.
  • For a distribution of ratings (5 stars 82%, 4 stars 11%...). Use a Meter per row; see the summary example.
  • For a bipolar or continuous attribute ("runs small ... runs large"). Use Slider.
  • For a single yes/no answer with no scale. Use Checkbox or Radio.
  • For content that cannot sensibly be scored, or where nobody is likely to answer. A control nobody uses is clutter; ask in a Textarea or leave it out.

Installation

Terminal
pnpm add @tessinaui/ui

Usage

example.tsx
import { Rating } from "@tessinaui/ui";
example.tsx
{/* Star (default) */}
<Rating label="Overall rating" defaultValue={4} />

{/* Half-star */}
<Rating label="Overall rating" precision={0.5} defaultValue={3.5} />

{/* NPS numeric scale — set min={0}, since NPS runs 0–10 */}
<Rating
  variant="numeric"
  label="Likelihood to recommend"
  min={0}
  max={10}
  lowLabel="Not likely"
  highLabel="Extremely likely"
/>

{/* Emoji reactions */}
<Rating variant="reaction" label="How do you feel?" />

{/* Binary thumbs */}
<Rating variant="binary" label="Was this helpful?" />

{/* Read-only display of an aggregate score */}
<Rating readOnly value={4.6} />

Examples

Default

The star scale, uncontrolled. label names the group for assistive technology — always pass it, or a screen reader announces only "Star rating".

Variants

Four shapes on one control. star for review scores, numeric for NPS and CSAT scales, reaction for quick sentiment, binary for a helpful/not-helpful answer. They share every prop; only the anatomy differs.

Sizes

Five steps from xs to xl. The boxes run the input family's ladder, so a md Rating lines up with a md Field in the same form row. Whatever the size you pick, every target is at least 24px with a mouse and 44px on a touch screen.

Intents

intent carries meaning, not decoration. Star keeps warning — amber stars are the universal convention — and every other shape defaults to the neutral none, which is what most real scales render.

Half-star precision

precision={0.5} lets someone pick a half. The half is real, not cosmetic: arrows step by 0.5 and the selected star announces "3.5 stars", so the value a screen-reader user hears is the value you receive.

A word for the value

showValue renders the readout; getValueLabel decides what it says. It is one seam for both the visible text and the accessible value, so a descriptor like "Very good" cannot drift from what assistive technology announces.

Anchor labels

lowLabel and highLabel pin the ends of the scale, which is what stops a 0–10 row being ambiguous about which end is good. They take any node, so an emoji pair works as well as words.

Labels under each item

itemLabelPlacement="below" captions every step. Use it when the steps are not self-evident — a five-point quality scale reads far better as Poor · Fair · Good · Very good · Exceptional than as five identical stars.

NPS scale

Net Promoter Score runs 0–10, so pass min={0}: an eleven-box scale, not a ten-box one. The row wraps rather than overflowing on a narrow screen.

Clearing the value

allowClear lets someone undo a rating — select the current value again, or press Delete. Without it a mis-tap is permanent, which is why the callback reports reason: "clear" separately from a normal selection.

Custom icons

icon and emptyIcon replace the star glyph. Keep both the same shape: the filled/empty distinction is carried by solid-vs-outline, which is what makes the scale readable without relying on colour alone.

Read-only display

A score you are showing is not a disabled input. readOnly renders one labelled image announcing "4.6 of 5", and the stars paint the exact fraction — 4.6 fills 60% of the fifth star rather than rounding to a half. Pair max={1} with your own number for the compact single-star form.

Summary with a breakdown

The average, the count and the per-star distribution are a composition, not props: a read-only Rating for the average and a Meter per row for the bars. The histogram is Meter's job, so it stays themeable and accessible on its own.

Inside a form

name renders a hidden input, so the value submits with the rest of the form; required and errorMessage wire the invalid state to the group. Multiple labelled rows are the standard shape for a multi-category review.

Rating, then follow-up

Asking for detail only after the score arrives is the pattern nearly every delivery and marketplace app uses — the first tap is cheap, and the follow-up is optional.

Binary as a form control

The thumbs pair is a real radiogroup with a name and a value. That is what separates it from the icon-only thumbs in a chat toolbar, which act immediately and belong to IconButton.

On a coloured surface

tone="on-color" is inherited from a Surface, so a rating inside a coloured panel derives every ink from that panel's own text colour. It needs no cooperation from the plate and is correct on a tint, a dark panel or a photo.

Right to left

Rating follows the direction it renders in: a dir on the page, on any ancestor or on the rating itself. Right to left, the first star sits on the right, a half star fills from its right edge, and ← moves to the next star, matching the visual order. Pass dir="ltr" to keep one rating left to right inside an RTL page, and a translated getValueLabel so the readout is localised too.

Controlled

Pass value with onValueChange to own the state. The second argument reports why the value changed — pointer, keyboard or clear — which is what you need to tell a deliberate reset from a fresh selection.

States

Read-only shows the value, disabled takes the control out of the form, and errorMessage marks the group invalid and describes it.

Skeleton

skeleton renders the placeholder from the component's own geometry, so the boxes, the radius and the mobile touch floor match what loads in — including the eleven boxes of a 0–10 scale.

Variants

Variant options

VariantPatternUse case
star★★★★☆Product ratings, review scores
numeric1 2 3 … 10NPS, customer satisfaction (CSAT)
reaction😡 😕 😐 🙂 😄App feedback, quick sentiment
binary👎 👍Helpful / not helpful

Intent options

none (default for numeric, reaction and binary), warning (default for star), error, success, info. primary is a deprecated alias for none.

Size options

xs, sm, md (default), lg, xl — the input family's ladder.

Rounded options

none, sm, md, lg, full. Defaults per variant: star sm, numeric md, reaction and binary full.

API Reference

Rating Props

PropTypeDefaultDescription
variant"star" | "numeric" | "reaction" | "binary""star"Visual pattern
valuenumber | null—Controlled value (null = nothing selected)
defaultValuenumber | nullnullUncontrolled initial value
onValueChange(value, { reason }) => void—Called when the value changes. reason is "pointer" | "keyboard" | "clear"
maxnumber5 (star, reaction, binary) / 10 (numeric)Highest value
min0 | 11Numeric only — lowest value. NPS runs from 0
size"xs" | "sm" | "md" | "lg" | "xl""md"Density
intent"none" | "warning" | "error" | "success" | "info""warning" (star) / "none" (rest)Semantic colour
tone"default" | "on-color"inherited from SurfaceSet on a coloured plate
readOnlybooleanfalseDisplay mode — role="img", exact fractional fill, no input
disabledbooleanfalseForm-disabled; the disabled tokens on the page, the plate's own ink dimmed on a coloured surface
precision1 | 0.51Star only — 0.5 enables half-star selection
allowClearbooleanfalseRe-selecting the current value (or Delete) clears it
dir"ltr" | "rtl"inheritedOverrides the direction inherited from the page. Mirrors the row, the half-star fill and the horizontal arrow keys
icon / emptyIconReactNode—Star only — custom filled / empty glyph
itemsRatingItem[]built-inReaction and binary — the option list
itemLabelPlacement"inline" | "below" | "hidden""inline" (reaction, binary) / "hidden" (star, numeric)Where each item's label sits. Hidden labels stay in the accessibility tree
showValuebooleanfalseRender the value readout
valuePosition"end" | "below""below"Where the readout sits
getValueLabel(value, max) => string"3 of 5" / "Not rated"The readout text and the read-only accessible name
getItemLabel(value, max) => string"3 stars" / "3 of 5"One item's accessible name
lowLabel / highLabelReactNode—Labels pinned to the ends of the scale
labelstringvariant defaultThe group's accessible name
namestring—Submit the value under this name (renders a hidden input)
requiredbooleanfalseMarks the group required for the form and assistive tech
errorMessageReactNode—Error text below the control; implies an invalid state
rounded"none" | "sm" | "md" | "lg" | "full"per variantCorner radius of each item
skeletonbooleanfalseRender the loading placeholder instead
classNamestring—Additional classes on the root

Deprecated for one minor, still working: onChange (use onValueChange), showLabel (use showValue for the readout and label for the name), and intent="primary" (use intent="none"). Each warns once in development.

RatingItem

Used by items for the reaction and binary variants.

FieldTypeDescription
valuenumberUnique option value
labelstringThe option's accessible name, and its visible label unless hidden
iconReactNodeOptional glyph rendered before the label

RatingSkeleton Props

variant, size, rounded, max, min, items, dir — the geometry props, so the placeholder is the same box as the component. Plus label: the skeleton is silent by default and only announces when you give it one, because a loading region should announce once, not once per placeholder.

Accessibility

  • Roles. The interactive control is a radiogroup of radios. Exactly one radio is ever checked — including at half precision, where the checked star announces the real value ("3.5 stars"). readOnly is not a disabled radiogroup: it renders a single role="img" labelled with the value, so a score is announced once instead of presenting options nobody can use.
  • Keyboard. The group is one Tab stop on every variant. ← → ↑ ↓ move the value (by precision on a star scale), Home and End jump to the ends, and Delete / Backspace clear when allowClear is set. Right to left, from the page or from dir, the horizontal arrows mirror, so ← moves forward.
  • Naming. Pass label. getValueLabel and getItemLabel are the localisation seams for the value and the individual options; items carry their own labels.
  • Forms. required sets aria-required, errorMessage sets aria-invalid and describes the group, and name submits the value through a hidden input.
  • Colour is never the only signal. Filled and empty are different glyphs (solid vs outline), not two tints of one colour, and the empty glyph is drawn in the boundary token gated at 3:1. On a coloured surface it takes the surface's muted ink, which clears 3:1 on every surface colour in both themes.
  • Targets. Every star, box and button is at least 24×24 with a mouse (WCAG 2.5.8) and 44×44 on a touch screen (2.5.5), at every size. A read-only rating is one image rather than a set of targets, so its stars keep their glyph's size.
  • VoiceOver passed 2026-09-14 (Safari). Focus reads "4 stars selected radio button, 4 of 5, Star rating radio group". Arrows move the selection and read it: "5 stars selected radio button, 5 of 5", then back to "3 stars selected radio button, 3 of 5". Half-star arrows passed on 2026-09-13 (TES-192). NVDA and JAWS are untested.
CircularSliderSelect

On this page

When to useWhen not to useInstallationUsageExamplesDefaultVariantsSizesIntentsHalf-star precisionA word for the valueAnchor labelsLabels under each itemNPS scaleClearing the valueCustom iconsRead-only displaySummary with a breakdownInside a formRating, then follow-upBinary as a form controlOn a coloured surfaceRight to leftControlledStatesSkeletonVariantsVariant optionsIntent optionsSize optionsRounded optionsAPI ReferenceRating PropsRatingItemRatingSkeleton PropsAccessibility