Skip to the content

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.

import { Rating } from "@booleanpress/ui/rating"

export default function RatingBasic() {
  return <Rating defaultValue={3} aria-label="Rate this reply" />
}

Half stars

allowHalf lets people choose the start half of a star, here 3.5.

import { Rating } from "@booleanpress/ui/rating"

export default function RatingHalfStars() {
  return <Rating allowHalf defaultValue={3.5} aria-label="Rate the delivery speed" />
}

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.

import { Rating } from "@booleanpress/ui/rating"

export default function RatingNumberOfStars() {
  return <Rating max={10} defaultValue={5} aria-label="Rate the onboarding from 1 to 10" />
}

Custom icon

icon and emptyIcon draw filled and outlined hearts in place of stars.

import { HeartIcon } from "lucide-react"
import { Rating } from "@booleanpress/ui/rating"

export default function RatingCustomIcon() {
  return (
    <Rating
      defaultValue={4}
      icon={<HeartIcon className="fill-current" />}
      emptyIcon={<HeartIcon />}
      aria-label="How much do you like the new editor"
    />
  )
}

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.

import { Rating } from "@booleanpress/ui/rating"

export default function RatingDisabled() {
  return <Rating disabled defaultValue={3} aria-label="Rate this reply" />
}

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" of role="radio" buttons, one per star (two per star with allowHalf), each with aria-checked. Read only, it is a single role="img" named with the value; the stars inside are hidden from screen readers.
Labels
Name the group with aria-label or aria-labelledby. Each star is named "3 of 5" from the provider string ratingValue; a half is "3.5 of 5". The read-only image is named with your aria-label and 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 --ring outline 2px round it.
Known limits
  • Clearing is not part of the radio group pattern: a screen reader hears the star become unchecked, and the ratingCleared message, 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 lg or 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.

Keyboard

Keyboard
KeyBehaviour
TabMoves 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.
SpaceChooses the focused star when it is not chosen yet; on the chosen star, clears the rating to 0 (unless allowClear={false}).
EnterChooses the focused star; on the chosen star, clears the rating to 0 (unless allowClear={false}).
BackspaceorDeleteClears the rating to 0 and announces it (unless allowClear={false}).

API

Rating

Renders a div and passes it every other prop.

Rating props
PropTypeDefaultDescription
allowClearbooleantrueChoosing 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.
allowHalfbooleanfalseLets people choose half stars: each star takes two radios, its start half and its whole.
defaultValuenumber0The rating at the start, when it controls itself. 0 by default.
dir"ltr" | "rtl"
disabledbooleanDims the stars and ignores the pointer and the keyboard.
emptyIconReactNodeThe mark of an unchosen star, in place of the outlined star. Defaults to icon, else the outlined star.
formstring
iconReactNodeThe mark of a chosen star, in place of the filled star.
maxnumber5The number of stars. 5 by default.
namestringThe 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"horizontalvertical stacks the stars. horizontal by default.
readOnlybooleanfalseShows the rating without letting it change: an image named with the value, not a radio group.
requiredbooleanThe 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.
valuenumberThe 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.

Theme tokens
TokenUsed for
--muted-foregroundtext
--primarytext
--ringoutline