caliper uidocs

Rating

Free

Stars that are a native radio group: arrows change the value, a pointer previews, half steps when you need them, and a read-only average that says how many ratings it comes from.

Installs components/ui/rating.tsxDepends on none beyond ReactMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/rating.json

Cabin at Lake Bled

How was your stay?
Cleanliness
Check-in (not available for this booking)
Choose a star to rate your stay.

When to use #

Use it for

  • Reviews and feedback with a clear 1–5 scale.
  • Showing a product's average next to its review count.

Skip it when

  • Quick thumbs up or down; use the feedback widget.
  • Ratings that must be justified; ask a question with radio cards.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/rating.json

Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above.

example.tsx
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Rating } from "@/components/ui/rating"

const stayWords = ["Poor", "Fair", "Good", "Great", "Loved it"];

export function RatingDemo() {
  const [stay, setStay] = useState(0);
  const [cleanliness, setCleanliness] = useState(4.5);
  const [sent, setSent] = useState(false);
  return (
    <div className="grid w-full max-w-md gap-6">
      <div className="flex flex-wrap items-center gap-3 border-b border-border pb-5">
        <div className="grid gap-1">
          <p className="text-sm font-semibold">Cabin at Lake Bled</p>
          <Rating label="Average rating" readOnly value={4.62} count={1284} size="sm" />
        </div>
      </div>
      <form
        className="grid gap-5"
        onSubmit={(event) => {
          event.preventDefault();
          setSent(true);
        }}
      >
        <Rating label="How was your stay?" labels={stayWords} value={stay} onValueChange={(value) => { setStay(value); setSent(false); }} name="stay" size="lg" />
        <Rating label="Cleanliness" step={0.5} value={cleanliness} onValueChange={setCleanliness} name="cleanliness" />
        <Rating label="Check-in (not available for this booking)" defaultValue={3} disabled />
        <div className="flex flex-wrap items-center gap-3">
          <Button type="submit" disabled={stay === 0}>Send review</Button>
          <span role="status" className="text-[13px] text-muted-foreground">
            {sent ? `Thanks. You rated your stay ${stay} of 5 and cleanliness ${cleanliness} of 5.` : stay === 0 ? "Choose a star to rate your stay." : "Press Delete on a star to clear it."}
          </span>
        </div>
      </form>
    </div>
  );
}

API reference #

Rating

component
PropTypeDefaultDescription
label *string—Names the group, e.g. "Rate this recipe". Shown as the legend unless `hideLabel`.
hideLabelbooleanfalse
valuenumber—
defaultValuenumber0
onValueChange((value: number) => void)—
maxnumber5Number of stars.
step1 | 0.510.5 allows half stars.
namestring—Submitted with the form.
disabledboolean—
readOnlybooleanfalseDisplay only: one image with the value in words, e.g. a product's average.
labelsreadonly string[]—A word for each whole star, shown beside the stars, e.g. ["Poor", "Fair", "Good", "Great", "Loved it"].
clearablebooleantrueChoosing the current value again, or Delete, clears it.
countnumber—Read-only: how many ratings the average comes from.
showValuebooleantrueRead-only: show the number after the stars.
size"sm" | "md" | "lg""md"
classNamestring—

Accessibility #

It exposes img semantics, with aria-hidden and aria-label kept in step with state.

No custom key handling; Tab and the native activation keys do the work.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.