# 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"`
- **Radix Radio Group:** <https://www.radix-ui.com/primitives/docs/components/radio-group>
- **APG Radio Group:** <https://www.w3.org/WAI/ARIA/apg/patterns/radio/>
- **Page:** <https://ui.booleanpress.com/components/rating> · @booleanpress/ui 0.2.0

## 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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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

| 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}`). |
| Backspace or Delete | 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.

| Token | Used for |
| --- | --- |
| `--muted-foreground` | text |
| `--primary` | text |
| `--ring` | outline |
