Buttons and controls
Rating
Give a score from a row of stars, or show one.
import { Rating } from "macbetter/rating"
export default function Example() {
return <Rating aria-label="Your rating" defaultValue={4} />
}When to use it
Use a rating to collect a quick score, like how much you liked a recipe, or to show an average, like an app's ratings. Five stars is the convention people know; keep it unless your scale means something different.
- Show the number beside a read-only rating, and how many ratings it's based on.
- Let people clear their score with
deselectablewhen no score is a valid answer.
Usage
import { Rating } from "macbetter/rating"Rating draws its own stars. Name it with aria-label or a Field label.
Half stars
step={0.5} splits each star in two. Pressing the first half of a star picks the half score.
import { Rating } from "macbetter/rating"
export default function Example() {
return <Rating aria-label="Rate this episode" step={0.5} defaultValue={3.5} />
}Read-only
readOnly shows any score, like an average of 4.6, as an image named by getValueLabel. Its empty stars are solid and quieter, so it doesn't look pressable.
4.6
12,408 Ratings
"use client"
import { Rating } from "macbetter/rating"
export default function Example() {
return (
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
<span style={{ font: "var(--mb-text-title-2)", fontVariantNumeric: "tabular-nums" }}>4.6</span>
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
<Rating readOnly size="sm" defaultValue={4.6} getValueLabel={(value, max) => `${value} out of ${max} stars`} />
<span style={{ color: "var(--mb-label-secondary)", font: "var(--mb-text-caption)" }}>12,408 Ratings</span>
</div>
</div>
)
}Sizes
import { Rating } from "macbetter/rating"
export default function Example() {
return (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 12 }}>
<Rating size="sm" aria-label="Rate this song" defaultValue={3} />
<Rating size="md" aria-label="Rate this album" defaultValue={3} />
<Rating size="lg" aria-label="Rate this app" defaultValue={3} />
</div>
)
}Controlled
Tap a star to rate
"use client"
import { Rating } from "macbetter/rating"
import { useState } from "react"
const labels = ["Tap a star to rate", "Hated it", "Didn't like it", "It was fine", "Liked it", "Loved it"]
export default function Example() {
const [score, setScore] = useState<number | null>(null)
return (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 10 }}>
<Rating aria-label="Rate this recipe" size="lg" deselectable value={score} onValueChange={setScore} />
<span style={{ color: "var(--mb-label-secondary)" }}>{labels[score ?? 0]}</span>
</div>
)
}Platform behavior
- macOS. 16-point stars. Moving the pointer over them previews the score in a lighter accent; moving away shows the chosen score again.
- iPadOS and iOS. 24-point stars with more room between them. Touch has no preview: a tap picks the score at once, and the star gives a little under the finger.
- Every platform. Stars fill with the accent from the leading edge, so half and partial stars mirror in right-to-left text.
Accessibility
- A rating is a radio group: one tab stop, and the arrow keys move and pick a score. Each star's radio is named "3 stars", or by
getValueLabel. - A read-only rating has the
imgrole and one name for the whole score. - The focus ring goes around the focused star.
API
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "md" | "lg" | "md" | Star size. |
value | number | null | — | The score. Pass it to control the rating. |
defaultValue | number | null | — | The score at first. |
onValueChange | (value, details) => void | — | Runs when the score wants to change. |
max | number | 5 | How many stars. |
step | number | 1 | The smallest change, as a fraction of a star. |
deselectable | boolean | false | Lets a press on the checked star clear the score. |
readOnly | boolean | false | Shows the score as an image. |
getValueLabel | (value, max) => string | — | Names a score, for each star and for a read-only rating. |
disabled | boolean | false | Can't be focused or changed. |
name | string | — | The name the score submits under. |
children | ReactNode | — | A RatingItem with your own symbol, repeated once per star. |
Data attributes
| Attribute | On | Values |
|---|---|---|
data-size | Rating | sm, md, lg |
data-filled | RatingItem | Present on stars the score covers whole |
data-partial | RatingItem | Present on the star the score ends partway through |
data-checked | RatingItem | Present on the star the score ends in |
data-highlighted | RatingItem | Present on stars up to a previewed score |
data-readonly, data-disabled | Both | Present in those states |
CSS variables
| Variable | macOS | iOS | What it sets |
|---|---|---|---|
--mb-rating-size | 16px | 24px | Star size |
--mb-rating-size-sm / -lg | 12px / 22px | 14px / 32px | Small and large stars |
--mb-rating-gap | 2px | 4px | Space between stars |
--mb-rating-color | --mb-accent | --mb-accent | Filled stars, set on the rating |
--rating-fill | From ui-sdk: how much of each star is filled, 0 to 1 |