Rating
Star rating with keyboard support, half values, hover preview and a read-only mode for reviews.
'use client';
import { Rating } from '@akhatmirzo/ui';
export default function RatingBasic() {
return <Rating defaultValue={4} />;
}
Import
import { Rating } from '@akhatmirzo/ui';When to use
- Rating a product, service or courier — users give feedback in a single click.
- Showing an average score in a review list — use
readOnly(fractions are drawn exactly: 4.3 → 4 full stars and 0.3 of the next). - When you need an exact number or a wide scale (0–100), a
Sliderworks better. - For options with distinct meanings ("Bad / OK / Good"), use a
SegmentedControlor aRadioGroup.
Half values
With allowHalf the left half of each star gives x.5 and the right half gives x; the arrow keys
move in 0.5 steps. Half stars are drawn with clip-path.
'use client';
import { Rating } from '@akhatmirzo/ui';
import { useState } from 'react';
export default function RatingHalf() {
const [value, setValue] = useState(3.5);
return (
<div className="flex items-center gap-3">
<Rating value={value} onValueChange={setValue} allowHalf size="lg" />
<span className="text-sm text-text-muted tabular-nums">{value} / 5</span>
</div>
);
}
Read-only
readOnly isn't interactive: the element is read as a single image with role="img" and
aria-label="4.5 / 5". The value isn't rounded — 3.7 is drawn as exactly 3.7 stars.
- Dilnoza
Yetkazib berish tez, sifat zo'r.
- Jasur
O'lchami biroz katta keldi.
'use client';
import { Rating } from '@akhatmirzo/ui';
const reviews = [
{ name: 'Dilnoza', rating: 5, text: "Yetkazib berish tez, sifat zo'r." },
{ name: 'Jasur', rating: 3.5, text: "O'lchami biroz katta keldi." },
];
export default function RatingReadOnly() {
return (
<ul className="m-0 flex w-full max-w-sm list-none flex-col gap-4 p-0">
{reviews.map((r) => (
<li key={r.name} className="flex flex-col gap-1">
<div className="flex items-center justify-between gap-3">
<span className="text-sm font-medium text-text">{r.name}</span>
<Rating value={r.rating} readOnly size="sm" />
</div>
<p className="m-0 text-sm text-text-muted">{r.text}</p>
</li>
))}
</ul>
);
}
Color, icon and size
color— the filled star color:warning(default),primary,danger. Empty stars use thetext-mutedcolor.icon— any icon instead of the built-in star (an SVG painted withcurrentColor).size—sm16px,md20px (default),lg28px.max— the number of stars (default 5).
'use client';
import { Rating } from '@akhatmirzo/ui';
import { Heart } from 'lucide-react';
export default function RatingCustom() {
return (
<div className="flex flex-col gap-4">
<Rating
defaultValue={3}
color="danger"
icon={<Heart fill="currentColor" strokeWidth={0} />}
/>
<Rating defaultValue={2} color="primary" size="sm" />
<Rating defaultValue={7} max={10} />
<Rating defaultValue={3} disabled />
</div>
);
}
Clearing and Field
With allowClear (default true), clicking the selected star again resets the value to 0. For a
required rating set allowClear={false}. Inside a Field the label names the
group and the description goes into aria-describedby.
1 dan 5 gacha baholang
'use client';
import { Field, Rating } from '@akhatmirzo/ui';
export default function RatingWithField() {
return (
<Field label="Xizmat sifati" description="1 dan 5 gacha baholang">
<Rating defaultValue={5} allowClear={false} />
</Field>
);
}
Props
Other <div> attributes and ref go to the root element.
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | — | Value (controlled) |
defaultValue | number | 0 | Initial value |
onValueChange | (value: number) => void | — | Called when the value changes |
max | number | 5 | Number of stars |
allowHalf | boolean | false | 0.5 steps |
allowClear | boolean | true | Clicking the selected value again resets it to 0 |
readOnly | boolean | false | Display only: role="img" + aria-label |
disabled | boolean | — | Disabled state (not focusable) |
size | 'sm' | 'md' | 'lg' | 'md' | Star size 16 / 20 / 28px |
icon | ReactNode | star SVG | Icon instead of the star |
color | 'warning' | 'primary' | 'danger' | 'warning' | Filled star color |
labels | { value?: (value: number, max: number) => string; group?: string } | '${v} / ${max}', 'Baho' | Radio and read-only name; group name (without Field or aria-label) |
Data attributes
| Attribute | Where | Value |
|---|---|---|
data-size | root | sm · md · lg |
data-hover | root | present while hovering the stars |
data-readonly | root | present in readOnly |
data-disabled | root | present when disabled |
data-state | radio | checked · unchecked |
data-state | star | full · partial · empty |
Keyboard
| Key | Action |
|---|---|
Tab | Enter the group (on the selected star) |
→ ↑ / ← ↓ | Increase / decrease by one step (and select) |
Home / End | Lowest (1 or 0.5) / highest value |
Space / Enter | Select the focused star (again — clear) |
Accessibility
- Interactive —
role="radiogroup", each value is arole="radio"(aria-checked,aria-label="3 / 5"); withallowHalfeach star holds two radios. Roving tabindex: oneTabstop for the whole group. - Name. The
Fieldlabel viaaria-labelledby, otherwisearia-labelorlabels.group("Baho"). - readOnly —
role="img"witharia-label(labels.value); the stars arearia-hidden. - Disabled —
aria-disabled; the radios aren't focusable.