Rating Group
Rating group allows a user to assign rating to an item within a product.
Features
- Support for syncing
disabled
state of withfieldset
element - Support for form
reset
events
Installation
To use the rating group machine in your project, run the following command in your command line:
npm install @zag-js/rating-group @zag-js/react # or yarn add @zag-js/rating-group @zag-js/react
npm install @zag-js/rating-group @zag-js/solid # or yarn add @zag-js/rating-group @zag-js/solid
npm install @zag-js/rating-group @zag-js/vue # or yarn add @zag-js/rating-group @zag-js/vue
npm install @zag-js/rating-group @zag-js/vue # or yarn add @zag-js/rating-group @zag-js/vue
This command will install the framework agnostic rating logic and the reactive utilities for your framework of choice.
Anatomy
To set up the rating correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-part
attribute to help identify them in the DOM.
Usage
First, import the rating package into your project
import * as rating from "@zag-js/rating-group"
The rating package exports two key functions:
machine
— The state machine logic for the rating widget.connect
— The function that translates the machine's state to JSX attributes and event handlers.
You'll also need to provide a unique
id
to theuseMachine
hook. This is used to ensure that every part has a unique identifier.
Next, import the required hooks and functions for your framework and use the rating machine in your project 🔥
import * as rating from "@zag-js/rating-group" import { useMachine, normalizeProps } from "@zag-js/react" import { HalfStar, Star } from "./icons" function Rating() { const [state, send] = useMachine(rating.machine({ id: "1" })) const api = rating.connect(state, send, normalizeProps) return ( <div {...api.rootProps}> <label {...api.labelProps}>Rate:</label> <div {...api.controlProps}> {api.items.map((index) => { const state = api.getItemState({ index }) return ( <span key={index} {...api.getItemProps({ index })}> {state.isHalf ? <HalfStar /> : <Star />} </span> ) })} </div> <input {...api.hiddenInputProps} /> </div> ) }
import * as rating from "@zag-js/rating-group" import { useMachine, normalizeProps } from "@zag-js/solid" import { createMemo, createUniqueId } from "solid-js" import { HalfStar, Star } from "./icons" function Rating() { const [state, send] = useMachine(rating.machine({ id: createUniqueId() })) const api = createMemo(() => rating.connect(state, send, normalizeProps)) return ( <div {...api().rootProps}> <label {...api().labelProps}>Rate:</label> <div {...api().controlProps}> {api().items.map((index) => { const state = api().getItemState(index) return ( <span key={index} {...api().getItemProps({ index })}> {state.isHalf ? <HalfStar /> : <Star />} </span> ) })} </div> <input {...api().hiddenInputProps} /> </div> ) }
import * as rating from "@zag-js/rating-group" import { normalizeProps, useMachine } from "@zag-js/vue" import { defineComponent, h, Fragment, computed } from "vue" import { HalfStar, Star } from "./icons" export default function defineComponent({ name: "Rating" setup() { const [state, send] = useMachine(rating.machine({ id: "rating" })) const apiRef = computed(() => rating.connect(state.value, send, normalizeProps), ) return () => { const api = apiRef.value return ( <div {...api.rootProps}> <label {...api.labelProps}>Rate:</label> <div {...api.controlProps}> {api.items.map((index) => { const state = api.getItemState({ index }) return ( <span key={index} {...api.getItemProps({ index })}> {state.isHalf ? <HalfStar /> : <Star />} </span> ) })} </div> <input {...api.hiddenInputProps} /> </div> ) } } })
<script setup> import * as rating from "@zag-js/rating-group" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed } from "vue" import { HalfStar, Star } from "./icons" const [state, send] = useMachine(rating.machine({ id: "1" })) const api = computed(() => rating.connect(state.value, send, normalizeProps)) </script> <template> <div v-bind="api.rootProps"> <label v-bind="api.labelProps">Rate:</label> <div v-bind="api.controlProps"> <span v-for="index in api.items" :key="index" v-bind="api.getItemProps({ index })" > <HalfStar v-if="api.getItemState({ index }).isHalf" /> <Star v-else /> </span> </div> <input v-bind="api.hiddenInputProps" /> </div> </template>
Disabling the rating group
To make rating disabled, set the context's disabled
property to true
const [state, send] = useMachine( rating.machine({ disabled: true, }), )
Making the rating readonly
To make rating readonly, set the context's readOnly
property to true
const [state, send] = useMachine( rating.machine({ readOnly: true, }), )
Setting the initial value
To set the rating's initial value, set the context's value
property.
const [state, send] = useMachine( rating.machine({ value: 2.5, }), )
Listening for changes
When the rating value changes, the onValueChange
callback is invoked.
const [state, send] = useMachine( rating.machine({ onValueChange({ value }) { console.log("rating value is:", value) // '1' | '2.5' | '4' }, }), )
Usage within forms
To use rating within forms, use the exposed inputProps
from the connect
function and ensure you pass name
value to the machine's context. It will
render a hidden input and ensure the value changes get propagated to the form
correctly.
const [state, send] = useMachine( rating.machine({ name: "rating", }), )
Styling guide
Earlier, we mentioned that each rating part has a data-part
attribute added to
them to select and style them in the DOM.
Disabled State
When the rating is disabled, the data-disabled
attribute is added to the
rating, control and label parts.
[data-part="rating"][data-disabled] { /* styles for rating disabled state */ } [data-part="label"][data-disabled] { /* styles for rating control disabled state */ } [data-part="input"][data-disabled] { /* styles for rating label disabled state */ }
Checked State
When the rating is checked, the data-checked
attribute is added to the rating
part.
[data-part="rating"][data-checked] { /* styles for rating checked state */ }
Readonly State
When the rating is readonly, the data-readonly
attribute is added to the
rating part.
[data-part="rating"][data-readonly] { /* styles for rating readonly state */ }
Highlighted
When a rating is highlighted, the data-highlighted
attribute is added to the
rating part.
[data-part="rating"][data-highlighted] { /* styles for highlighted rating */ }
Half rating
When a rating is half, the data-half
attribute is added to the rating part.
[data-part="rating"][data-half] { /* styles for half rating */ }
Methods and Properties
Machine Context
The rating group machine exposes the following context properties:
ids
Partial<{ root: string; label: string; hiddenInput: string; control: string; rating(id: string): string; }>
The ids of the elements in the rating. Useful for composition.translations
IntlTranslations
Specifies the localized strings that identifies the accessibility elements and their statescount
number
The total number of ratings.name
string
The name attribute of the rating element (used in forms).form
string
The associate form of the underlying input element.value
number
The current rating value.readOnly
boolean
Whether the rating is readonly.disabled
boolean
Whether the rating is disabled.allowHalf
boolean
Whether to allow half stars.autoFocus
boolean
Whether to autofocus the rating.onValueChange
(details: ValueChangeDetails) => void
Function to be called when the rating value changes.onHoverChange
(details: HoverChangeDetails) => void
Function to be called when the rating value is hovered.dir
"ltr" | "rtl"
The document's text/writing direction.id
string
The unique identifier of the machine.getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The rating group api
exposes the following methods:
setValue
(value: number) => void
Sets the value of the rating groupclearValue
() => void
Clears the value of the rating grouphovering
boolean
Whether the rating group is being hoveredvalue
number
The current value of the rating grouphoveredValue
number
The value of the currently hovered ratingcount
number
The total number of ratingsitems
number[]
The array of rating values. Returns an array of numbers from 1 to the max value.getItemState
(props: ItemProps) => ItemState
Returns the state of a rating item
Accessibility
Keyboard Interactions
- ArrowRightMoves focus to the next star, increasing the rating value based on the `allowHalf` property.
- ArrowLeftMoves focus to the previous star, decreasing the rating value based on the `allowHalf` property.
- EnterSelects the focused star in the rating group.
Edit this page on GitHub