implement
Rating Group

Rating Group

Pick a rating from a range of values.

3 out of 5
import { Div, signal, Span } from "@implementjs/core";
import { RatingGroup, RatingGroupItem } from "@/lib/components/ui/rating-group";

export default function RatingGroupDemo() {
	const value = signal(3);

	return Div(
		{ class: "flex flex-col items-center gap-2" },
		RatingGroup(
			{ value, "aria-label": "Rate this library" },
			...Array.from({ length: 5 }, (_, index) => RatingGroupItem({ index })),
		),
		Span(
			{ class: "text-sm text-muted-foreground tabular-nums" },
			value.bind((v) => `${v} out of 5`),
		),
	);
}

A rating group is a row of steps — usually stars — for choosing a value out of a maximum. RatingGroup is the root and the single focusable control; each RatingGroupItem is one visual step you fill with an icon.

import { RatingGroup, RatingGroupItem } from "@implementjs/primitives";

RatingGroup(
	{ "aria-label": "Rate this product" },
	...Array.from({ length: 5 }, (_, index) => RatingGroupItem({ index }, StarIcon())),
);

Each part accepts optional props and children — pass a props object when you need attributes, or pass children directly. See createComponent. Extra props are forwarded onto the underlying Divs. Items take a zero-based index; the item represents the rating index + 1.

Value and range

value defaults to 0 inside a range from min (0) to max (5). Pass a number to seed it, or a signal to control it from outside:

const rating = signal(3);

RatingGroup(
	{ value: rating, "aria-label": "Rate this product" },
	...Array.from({ length: 5 }, (_, index) => RatingGroupItem({ index })),
);

Clicking a step sets the value. Clicking the first step when it is already the value clears the rating back to 0 (when min is 0).

Half steps and hover

Pass allowHalf to work in half steps: the pointer's position inside an item picks the half, arrow keys move by 0.5, and an item halfway filled gets data-state="partial".

While the pointer moves across the group, the steps preview the value it would take; the preview reverts on leave. Pass hoverPreview: false to turn that off. readonly shows a value that cannot be changed, and disabled also removes the group from the Tab order.

Keyboard

The root is a single Tab stop. Arrow keys adjust the value by one step (or half with allowHalf), Home and End jump to min and max, PageUp and PageDown always move by a whole step, and typing a number picks it directly.

Accessibility

The root announces as a slider — role="slider" with aria-valuenow, aria-valuemin, and aria-valuemax — because a rating is one value out of a range, not a set of separate buttons. Items are role="presentation". Two things to know:

  • The default aria-label is "Rating" and the default aria-valuetext reads "3 out of 5". Pass your own to be specific: aria-label: "Rate this product".
  • Keep the icons inside items aria-hidden; the root carries all the semantics.

Styling

The root sets data-rating-group-root and items set data-rating-group-item, with data-state as "active", "partial", or "inactive", plus data-value, data-orientation, data-disabled, and data-readonly:

RatingGroupItem(
	{ index, class: "group/star" },
	StarIcon({ "aria-hidden": true, class: "size-5 group-data-[state=active]/star:fill-current" }),
);

Fill against data-state — solid for "active", half for "partial" when you use allowHalf.

API Reference

RatingGroup

The root and the single focusable control. Announces as a slider: role="slider" with the aria value attributes. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
valueSignal<number> | number0The current rating. Pass a signal to control it from outside; a number seeds uncontrolled state.
minnumber0The lowest value the rating can take.
maxnumber5The highest value the rating can take.
allowHalfbooleanfalseWork in half steps: pointer position picks the half, arrows move by 0.5.
readonlybooleanfalseThe value can be read but not changed.
disabledSignal<boolean> | booleanfalsePrevents changes and removes the group from the Tab order.
hoverPreviewbooleantruePreview the value under the pointer before clicking.
orientation"horizontal" | "vertical""horizontal"The axis pointer positions are measured along for half steps.
requiredbooleanfalseSets aria-required on the group.
Data attributeValue
[data-rating-group-root]Present
[data-orientation]"horizontal" | "vertical"
[data-disabled]Present when disabled
[data-readonly]Present when readonly

RatingGroupItem

One visual step. role="presentation" — the root carries the semantics. Fill it with an icon and style against data-state. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
index*numberZero-based position; the item represents the rating index + 1.
disabledSignal<boolean> | booleanfalseIgnores pointer input on this item.
Data attributeValue
[data-rating-group-item]Present
[data-state]"active" | "partial" | "inactive"
[data-value]The rating the item represents
[data-orientation]"horizontal" | "vertical"
[data-disabled]Present when disabled
[data-readonly]Present when readonly