# Rating

**When to use:** A star rating input or display, for review scores, feedback prompts, and quality indicators. Use `readOnly` to just show an existing rating (e.g. a product's average score) rather than collect one.

**Keywords:** stars, ranking, thumbs up

**Import:** `import { Rating } from '@ahrowe/ui'`
**Types:** `import type { RatingProps } from '@ahrowe/ui'`

```tsx
import { useState } from 'react';
import { Rating } from '@ahrowe/ui';

// Controlled
const [value, setValue] = useState(3);
<Rating value={value} onChange={setValue} />

// Uncontrolled
<Rating defaultValue={4} onChange={(value) => console.log(value)} />

// Half-star increments
<Rating defaultValue={3.5} allowHalf />

// A different number of stars
<Rating defaultValue={6} max={10} />

// Read-only, for displaying an existing rating (e.g. a product card)
<Rating value={4} readOnly />

// Disabled
<Rating value={2} disabled />

// Larger, via font-size (sizing is em-based, like Switch and Avatar)
<Rating defaultValue={4} style={{ fontSize: 32 }} />

// Custom icon
import { faHeart } from '@fortawesome/free-solid-svg-icons';
<Rating defaultValue={3} icon={faHeart} />

// Disable click-to-clear (clicking the currently-selected star normally resets to 0)
<Rating value={value} onChange={setValue} allowClear={false} />
```

**Interaction:** click, tap, or drag/swipe across the stars to set the rating, on any pointer type (mouse, touch, pen) via the Pointer Events API. Dragging updates the preview live and commits on release, which is the natural gesture on mobile rather than requiring a precise tap on one star. A plain click/tap on the currently-selected star clears it to `0` (set `allowClear={false}` to keep it fixed instead); a drag that happens to end back near its starting value doesn't clear, since that's a different gesture from a deliberate re-tap. With keyboard focus: `←`/`↓` and `→`/`↑` move by one star (or half a star with `allowHalf`), `Home`/`End` jump to `0`/`max`.

**Sizing:** there's no dedicated size prop, `font-size` scales the whole control (like `Switch` and `Avatar`). The root sizes to its content (`width: fit-content`) rather than stretching to fill a flex/grid ancestor, since the click/drag position is measured against the root's own width.

**Key props:**

| Prop | Type | Description |
|------|------|-------------|
| `value` | `number` | Controlled value (omit for uncontrolled) |
| `defaultValue` | `number` | Initial value when uncontrolled (default `0`) |
| `onChange` | `(value: number) => void` | Fires when the rating changes, from a click or keyboard input |
| `max` | `number` | Number of stars (default `5`) |
| `allowHalf` | `boolean` | Allow half-star increments (default `false`) |
| `allowClear` | `boolean` | Clicking the currently-selected star resets the value to `0` (default `true`) |
| `readOnly` | `boolean` | Display only, not focusable or interactive |
| `disabled` | `boolean` | Dims the control and disables interaction |
| `icon` | `IconDefinition \| ReactElement` | Overrides the default star icon — FontAwesome icon or any React element |
| `aria-label` | `string` | Accessible label (default `'Rating'`) |

**Accessibility:** renders `role="slider"` with `aria-valuemin`/`aria-valuemax`/`aria-valuenow`/`aria-valuetext`, matching `Slider`'s pattern. To name it from visible text, pass `aria-labelledby`, which wins over the default `aria-label`.

**Theming:** override these CSS variables theme-wide via `ThemeProvider` or per instance via `style`; each falls back to a built-in default:

| Variable | Falls back to |
|----------|---------------|
| `--rating-filled-color` | `var(--primary-color)` |
| `--rating-empty-color` | `color-mix(in srgb, currentcolor 20%, transparent)`: a tint of the text colour, visible on a Card as on the page |
| `--rating-size` | `1.25x --default-font-size`, i.e. 20px at a 16px base |

**Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ Rating: { allowHalf: true } }}`. See [ConfigProvider.md](ConfigProvider.md).

**Slots:** `root` `item` `iconEmpty` `iconFilled`

## Sizing

The symbols, the gap between them and the partial-fill clip are all `em` against `--rating-size`, so setting it alone resizes the whole control.
