Skip to main content

Rating

Star ratings you can click, hover and drive with the keyboard. Half steps, read-only display and custom icons.

How was the 2.4 release?

1'use client';
2
3import * as React from 'react';
4import { Rating } from '@/components/ui/rating';
5
6export function Default() {
7 const [value, setValue] = React.useState(0);
8
9 return (
10 <div className="flex flex-col items-center gap-2">
11 <Rating value={value} onValueChange={setValue} aria-label="Rate this release" />
12 <p className="text-muted-foreground text-xs">
13 {value === 0 ? 'How was the 2.4 release?' : `You rated it ${value} out of 5`}
14 </p>
15 </div>
16 );
17}

Installation

pnpm dlx nachui add rating

Anatomy

1import { Rating } from '@/components/ui/rating';
1<Rating defaultValue={3} onValueChange={setScore} aria-label="Rate this release" />
The interactive rating is a radiogroup with one radio per step, so assistive tech reads it as a choice between values. Focus lands on the current value, the arrow keys move by one step, Home and End jump to the ends, and Backspace clears. Hovering previews a value without committing it.

Variants

Default

Controlled with value and onValueChange, or uncontrolled with defaultValue.

How was the 2.4 release?

1'use client';
2
3import * as React from 'react';
4import { Rating } from '@/components/ui/rating';
5
6export function Default() {
7 const [value, setValue] = React.useState(0);
8
9 return (
10 <div className="flex flex-col items-center gap-2">
11 <Rating value={value} onValueChange={setValue} aria-label="Rate this release" />
12 <p className="text-muted-foreground text-xs">
13 {value === 0 ? 'How was the 2.4 release?' : `You rated it ${value} out of 5`}
14 </p>
15 </div>
16 );
17}

Half steps

precision={0.5} splits each star. The half is picked from where the pointer is. allowClear resets to zero when the current value is clicked again.
1import { Rating } from '@/components/ui/rating';
2
3export function Half() {
4 return (
5 <Rating
6 defaultValue={3.5}
7 precision={0.5}
8 allowClear
9 aria-label="Rate the onboarding experience"
10 />
11 );
12}

Read only

readOnly renders an img with a label such as 4.6 of 5 and no buttons. Children render after the stars, for counts and averages.
Northwind Desk Mat
Mechanical Keyboard, 65%
Monitor Arm
1import { Rating } from '@/components/ui/rating';
2
3const reviews = [
4 { product: 'Northwind Desk Mat', score: 4.6, count: 1284 },
5 { product: 'Mechanical Keyboard, 65%', score: 4.2, count: 312 },
6 { product: 'Monitor Arm', score: 3.8, count: 97 },
7];
8
9export function ReadOnly() {
10 return (
11 <div className="flex w-full max-w-sm flex-col gap-3">
12 {reviews.map((review) => (
13 <div key={review.product} className="flex items-center justify-between gap-4">
14 <span className="text-sm">{review.product}</span>
15 <Rating readOnly value={review.score} precision={0.5} size="sm">
16 <span className="text-muted-foreground text-xs tabular-nums">
17 {review.score} ({review.count})
18 </span>
19 </Rating>
20 </div>
21 ))}
22 </div>
23 );
24}

Sizes

1import { Rating } from '@/components/ui/rating';
2
3export function Sizes() {
4 return (
5 <div className="flex flex-col items-start gap-4">
6 <Rating size="sm" defaultValue={4} aria-label="Small rating" />
7 <Rating size="default" defaultValue={4} aria-label="Default rating" />
8 <Rating size="lg" defaultValue={4} aria-label="Large rating" />
9 </div>
10 );
11}

Custom icon

icon and filledIcon swap the shape. Recolor the fill through the rating-fill slot.
1import { HeartIcon } from '@hugeicons/core-free-icons';
2import { HugeiconsIcon } from '@hugeicons/react';
3import { Rating } from '@/components/ui/rating';
4
5export function CustomIcon() {
6 return (
7 <Rating
8 max={5}
9 defaultValue={3}
10 aria-label="How much did you like it?"
11 icon={<HugeiconsIcon icon={HeartIcon} strokeWidth={1.5} />}
12 filledIcon={<HugeiconsIcon icon={HeartIcon} strokeWidth={1.5} fill="currentColor" />}
13 className="[&_[data-slot=rating-fill]]:text-destructive"
14 />
15 );
16}

API Reference

Rating

PropTypeDefaultDescription
valuenumber-Controlled value
defaultValuenumber0Initial value when uncontrolled
onValueChange(value: number) => void-Called with the new value
maxnumber5Number of steps
precision1 | 0.51Whole or half steps
size"sm" | "default" | "lg""default"Icon size
readOnlybooleanfalseDisplay only
disabledbooleanfalseDims and blocks interaction
allowClearbooleanfalsePicking the current value clears it
namestring-Renders a hidden input for form submissions
iconReactNodestar outlineOutline icon
filledIconReactNodefilled starIcon drawn over the outline, clipped to the fill
getLabel(value: number, max: number) => string"3 of 5"Accessible label for each step and the group
classNamestring-Additional CSS classes
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue
Ctrl+I