Rating Group
Allows users to rate items using a set of icons.
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.
Examples
Learn how to use the RatingGroup
component in your project. Let's take a look at the most basic
example:
import { RatingGroup } from '@ark-ui/react/rating-group'
import { StarIcon } from 'lucide-react'
export const Basic = () => (
<RatingGroup.Root count={5} defaultValue={3}>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{({ items }) =>
items.map((item) => (
<RatingGroup.Item key={item} index={item}>
<RatingGroup.ItemContext>
{({ highlighted }) => (highlighted ? <StarIcon fill="current" /> : <StarIcon />)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
))
}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
import { RatingGroup } from '@ark-ui/solid/rating-group'
import { StarIcon } from 'lucide-solid'
import { Index, Show } from 'solid-js'
export const Basic = () => (
<RatingGroup.Root count={5} value={3}>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{(context) => (
<Index each={context().items}>
{(index) => (
<RatingGroup.Item index={index()}>
<RatingGroup.ItemContext>
{(context) => (
<Show when={context().highlighted} fallback={<StarIcon />}>
<StarIcon fill="current" />
</Show>
)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
)}
</Index>
)}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
<script setup lang="ts">
import { RatingGroup } from '@ark-ui/vue/rating-group'
import { StarIcon, StarOutlineIcon } from 'lucide-vue-next'
</script>
<template>
<RatingGroup.Root :count="5" :model-value="3">
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context v-slot="{ items }">
<RatingGroup.Item v-for="item in items" :key="item" :index="item">
<RatingGroup.ItemContext v-slot="{ highlighted }">
<StarIcon v-if="highlighted" />
<StarOutlineIcon v-else />
</RatingGroup.ItemContext>
</RatingGroup.Item>
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
</template>
Using half ratings
Allow 0.5
value steps by setting the allowHalf
prop to true
. Ensure to render the correct icon
if the isHalf
value is set in the Rating components render callback.
import { RatingGroup } from '@ark-ui/react/rating-group'
import { StarHalfIcon, StarIcon } from 'lucide-react'
export const HalfRatings = () => (
<RatingGroup.Root count={5} defaultValue={3} allowHalf>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{({ items }) =>
items.map((item) => (
<RatingGroup.Item key={item} index={item}>
<RatingGroup.ItemContext>
{({ half, highlighted }) => {
if (half) return <StarHalfIcon fill="current" />
if (highlighted) return <StarIcon fill="current" />
return <StarIcon />
}}
</RatingGroup.ItemContext>
</RatingGroup.Item>
))
}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
import { RatingGroup } from '@ark-ui/solid/rating-group'
import { StarHalfIcon, StarIcon } from 'lucide-solid'
import { Index } from 'solid-js'
export const HalfRatings = () => (
<RatingGroup.Root count={5} value={3} allowHalf>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{(context) => (
<Index each={context().items}>
{(index) => (
<RatingGroup.Item index={index()}>
<RatingGroup.ItemContext>
{(context) =>
context().half ? (
<StarHalfIcon fill="current" />
) : context().highlighted ? (
<StarIcon fill="current" />
) : (
<StarIcon />
)
}
</RatingGroup.ItemContext>
</RatingGroup.Item>
)}
</Index>
)}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
<script setup lang="ts">
import { RatingGroup } from '@ark-ui/vue/rating-group'
import { StarHalfIcon, StarIcon, StarOutlineIcon } from 'lucide-vue-next'
</script>
<template>
<RatingGroup.Root :count="5" :model-value="3" allowHalf>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context v-slot="{ items }">
<RatingGroup.Item v-for="item in items" :key="item" :index="item">
<RatingGroup.ItemContext v-slot="{ highlighted, half }">
<StarHalfIcon v-if="half" />
<StarIcon v-else-if="highlighted" />
<StarOutlineIcon v-else />
</RatingGroup.ItemContext>
</RatingGroup.Item>
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
</template>
Using a default value
import { RatingGroup } from '@ark-ui/react/rating-group'
import { StarIcon } from 'lucide-react'
export const InitialValue = () => (
<RatingGroup.Root count={5} defaultValue={2} readOnly>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{({ items }) =>
items.map((item) => (
<RatingGroup.Item key={item} index={item}>
<RatingGroup.ItemContext>
{({ highlighted }) => (highlighted ? <StarIcon fill="current" /> : <StarIcon />)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
))
}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
import { RatingGroup } from '@ark-ui/solid/rating-group'
import { StarIcon } from 'lucide-solid'
import { Index, Show } from 'solid-js'
export const InitialValue = () => (
<RatingGroup.Root count={5} value={2} readOnly>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{(context) => (
<Index each={context().items}>
{(index) => (
<RatingGroup.Item index={index()}>
<RatingGroup.ItemContext>
{(context) => (
<Show when={context().highlighted} fallback={<StarIcon />}>
<StarIcon fill="current" />
</Show>
)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
)}
</Index>
)}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
<script setup lang="ts">
import { RatingGroup } from '@ark-ui/vue/rating-group'
import { StarIcon, StarOutlineIcon } from 'lucide-vue-next'
</script>
<template>
<RatingGroup.Root :count="5" :model-value="2" readOnly>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context v-slot="{ items }">
<RatingGroup.Item v-for="item in items" :key="item" :index="item">
<RatingGroup.ItemContext v-slot="{ highlighted }">
<StarIcon v-if="highlighted" />
<StarOutlineIcon v-else />
</RatingGroup.ItemContext>
</RatingGroup.Item>
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
</template>
Controlled
When using the RatingGroup
component, you can use the value
and onValueChange
props to control
the state.
import { RatingGroup } from '@ark-ui/react/rating-group'
import { StarIcon } from 'lucide-react'
import { useState } from 'react'
export const Controlled = () => {
const [value, setValue] = useState(0)
return (
<RatingGroup.Root
count={5}
value={value}
onValueChange={(details) => setValue(details.value)}
allowHalf
>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{({ items }) =>
items.map((item) => (
<RatingGroup.Item key={item} index={item}>
<RatingGroup.ItemContext>
{({ highlighted }) => (highlighted ? <StarIcon fill="current" /> : <StarIcon />)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
))
}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
}
import { RatingGroup } from '@ark-ui/solid/rating-group'
import { StarIcon } from 'lucide-solid'
import { Index, Show, createSignal } from 'solid-js'
export const Controlled = () => {
const [value, setValue] = createSignal(0)
return (
<RatingGroup.Root
count={5}
value={value()}
onValueChange={(details) => setValue(details.value)}
>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{(context) => (
<Index each={context().items}>
{(index) => (
<RatingGroup.Item index={index()}>
<RatingGroup.ItemContext>
{(context) => (
<Show when={context().highlighted} fallback={<StarIcon />}>
<StarIcon fill="current" />
</Show>
)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
)}
</Index>
)}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
}
<script setup lang="ts">
import { RatingGroup } from '@ark-ui/vue/rating-group'
import { ref } from 'vue'
import { StarIcon, StarOutlineIcon } from 'lucide-vue-next'
const value = ref(0)
</script>
<template>
<RatingGroup.Root :count="5" v-model="value" allowHalf>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context v-slot="{ items }">
<RatingGroup.Item v-for="item in items" :key="item" :index="item">
<RatingGroup.ItemContext v-slot="{ highlighted }">
<StarIcon v-if="highlighted" />
<StarOutlineIcon v-else />
</RatingGroup.ItemContext>
</RatingGroup.Item>
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
</template>
Disabling the rating group
To make the rating group disabled, set the disabled
prop to true
.
import { RatingGroup } from '@ark-ui/react/rating-group'
import { StarIcon } from 'lucide-react'
export const Disabled = () => (
<RatingGroup.Root count={5} defaultValue={3} disabled>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{({ items }) =>
items.map((item) => (
<RatingGroup.Item key={item} index={item}>
<RatingGroup.ItemContext>
{({ highlighted }) => (highlighted ? <StarIcon fill="current" /> : <StarIcon />)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
))
}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
import { RatingGroup } from '@ark-ui/solid/rating-group'
import { StarIcon } from 'lucide-solid'
import { Index, Show } from 'solid-js'
export const Disabled = () => (
<RatingGroup.Root count={5} value={3} disabled>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{(context) => (
<Index each={context().items}>
{(index) => (
<RatingGroup.Item index={index()}>
<RatingGroup.ItemContext>
{(context) => (
<Show when={context().highlighted} fallback={<StarIcon />}>
<StarIcon fill="current" />
</Show>
)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
)}
</Index>
)}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
<script setup lang="ts">
import { RatingGroup } from '@ark-ui/vue/rating-group'
import { StarIcon, StarOutlineIcon } from 'lucide-vue-next'
</script>
<template>
<RatingGroup.Root :count="5" :model-value="3" disabled>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context v-slot="{ items }">
<RatingGroup.Item v-for="item in items" :key="item" :index="item">
<RatingGroup.ItemContext v-slot="{ highlighted }">
<StarIcon v-if="highlighted" />
<StarOutlineIcon v-else />
</RatingGroup.ItemContext>
</RatingGroup.Item>
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
</template>
Readonly rating group
To make the rating group readonly, set the readOnly
prop to true
.
import { RatingGroup } from '@ark-ui/react/rating-group'
import { StarIcon } from 'lucide-react'
export const ReadOnly = () => (
<RatingGroup.Root count={5} defaultValue={3} readOnly>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{({ items }) =>
items.map((item) => (
<RatingGroup.Item key={item} index={item}>
<RatingGroup.ItemContext>
{({ highlighted }) => (highlighted ? <StarIcon fill="currrent" /> : <StarIcon />)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
))
}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
import { RatingGroup } from '@ark-ui/solid/rating-group'
import { StarIcon } from 'lucide-solid'
import { Index, Show } from 'solid-js'
export const ReadOnly = () => (
<RatingGroup.Root count={5} value={3} readOnly>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{(context) => (
<Index each={context().items}>
{(index) => (
<RatingGroup.Item index={index()}>
<RatingGroup.ItemContext>
{(context) => (
<Show when={context().highlighted} fallback={<StarIcon />}>
<StarIcon fill="current" />
</Show>
)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
)}
</Index>
)}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
<script setup lang="ts">
import { RatingGroup } from '@ark-ui/vue/rating-group'
import { StarIcon, StarOutlineIcon } from 'lucide-vue-next'
</script>
<template>
<RatingGroup.Root :count="5" :model-value="3" readOnly>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context v-slot="{ items }">
<RatingGroup.Item v-for="item in items" :key="item" :index="item">
<RatingGroup.ItemContext v-slot="{ highlighted }">
<StarIcon v-if="highlighted" />
<StarOutlineIcon v-else />
</RatingGroup.ItemContext>
</RatingGroup.Item>
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
</template>
Usage within forms
To use the rating group within forms, pass the prop name
. It will render a hidden input and ensure
the value changes get propagated to the form correctly.
import { RatingGroup } from '@ark-ui/react/rating-group'
import { StarIcon } from 'lucide-react'
export const FormUsage = () => (
<RatingGroup.Root name="my-rating" count={5} defaultValue={3}>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{({ items }) =>
items.map((item) => (
<RatingGroup.Item key={item} index={item}>
<RatingGroup.ItemContext>
{({ highlighted }) => (highlighted ? <StarIcon fill="current" /> : <StarIcon />)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
))
}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
import { RatingGroup } from '@ark-ui/solid/rating-group'
import { StarIcon } from 'lucide-solid'
import { Index, Show } from 'solid-js'
export const FormUsage = () => (
<RatingGroup.Root name="my-rating" count={5} value={3}>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{(context) => (
<Index each={context().items}>
{(index) => (
<RatingGroup.Item index={index()}>
<RatingGroup.ItemContext>
{(context) => (
<Show when={context().highlighted} fallback={<StarIcon />}>
<StarIcon fill="current" />
</Show>
)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
)}
</Index>
)}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
)
<script setup lang="ts">
import { RatingGroup } from '@ark-ui/vue/rating-group'
import { StarIcon, StarOutlineIcon } from 'lucide-vue-next'
</script>
<template>
<RatingGroup.Root name="my-rating" :count="5" :model-value="3">
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context v-slot="{ items }">
<RatingGroup.Item v-for="item in items" :key="item" :index="item">
<RatingGroup.ItemContext v-slot="{ highlighted }">
<StarIcon v-if="highlighted" />
<StarOutlineIcon v-else />
</RatingGroup.ItemContext>
</RatingGroup.Item>
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
</template>
Using the Field Component
The Field
component helps manage form-related state and accessibility attributes of a rating group.
It includes handling ARIA labels, helper text, and error text to ensure proper accessibility.
import { Field } from '@ark-ui/react/field'
import { RatingGroup } from '@ark-ui/react/rating-group'
import { StarIcon } from 'lucide-react'
export const WithField = (props: Field.RootProps) => {
return (
<Field.Root {...props}>
<RatingGroup.Root count={5} defaultValue={3}>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{({ items }) =>
items.map((item) => (
<RatingGroup.Item key={item} index={item}>
<RatingGroup.ItemContext>
{({ highlighted }) =>
highlighted ? <StarIcon fill="current" /> : <StarIcon />
}
</RatingGroup.ItemContext>
</RatingGroup.Item>
))
}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
)
}
import { Field } from '@ark-ui/solid/field'
import { RatingGroup } from '@ark-ui/solid/rating-group'
import { StarIcon } from 'lucide-solid'
import { Index, Show } from 'solid-js'
export const WithField = (props: Field.RootProps) => {
return (
<Field.Root {...props}>
<RatingGroup.Root count={5} defaultValue={3}>
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context>
{(context) => (
<Index each={context().items}>
{(index) => (
<RatingGroup.Item index={index()}>
<RatingGroup.ItemContext>
{(context) => (
<Show when={context().highlighted} fallback={<StarIcon />}>
<StarIcon fill="current" />
</Show>
)}
</RatingGroup.ItemContext>
</RatingGroup.Item>
)}
</Index>
)}
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
)
}
<script setup lang="ts">
import { Field } from '@ark-ui/vue/field'
import { RatingGroup } from '@ark-ui/vue/rating-group'
import { StarIcon, StarOutlineIcon } from 'lucide-vue-next'
</script>
<template>
<Field.Root>
<RatingGroup.Root :count="5" :model-value="3">
<RatingGroup.Label>Label</RatingGroup.Label>
<RatingGroup.Control>
<RatingGroup.Context v-slot="{ items }">
<RatingGroup.Item v-for="item in items" :key="item" :index="item">
<RatingGroup.ItemContext v-slot="{ highlighted }">
<StarIcon v-if="highlighted" />
<StarOutlineIcon v-else />
</RatingGroup.ItemContext>
</RatingGroup.Item>
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
</template>
API Reference
Root
Prop | Default | Type |
---|---|---|
allowHalf | boolean Whether to allow half stars. | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
autoFocus | boolean Whether to autofocus the rating. | |
count | 5 | number The total number of ratings. |
defaultValue | number The initial value of the rating group when it is first rendered. Use when you do not need to control the state of the rating group. | |
disabled | boolean Whether the rating is disabled. | |
form | string The associate form of the underlying input element. | |
id | string The unique identifier of the machine. | |
ids | Partial<{
root: string
label: string
hiddenInput: string
control: string
item(id: string): string
}> The ids of the elements in the rating. Useful for composition. | |
name | string The name attribute of the rating element (used in forms). | |
onHoverChange | (details: HoverChangeDetails) => void Function to be called when the rating value is hovered. | |
onValueChange | (details: ValueChangeDetails) => void Function to be called when the rating value changes. | |
readOnly | boolean Whether the rating is readonly. | |
required | boolean Whether the rating is required. | |
translations | IntlTranslations Specifies the localized strings that identifies the accessibility elements and their states | |
value | number The current rating value. |
Control
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | rating-group |
[data-part] | control |
[data-readonly] | Present when read-only |
[data-disabled] | Present when disabled |
HiddenInput
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Item
Prop | Default | Type |
---|---|---|
index | number | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | rating-group |
[data-part] | item |
[data-disabled] | Present when disabled |
[data-readonly] | Present when read-only |
[data-checked] | Present when checked |
[data-highlighted] | Present when highlighted |
[data-half] |
Label
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | rating-group |
[data-part] | label |
[data-disabled] | Present when disabled |
RootProvider
Prop | Default | Type |
---|---|---|
value | UseRatingGroupReturn | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Accessibility
Keyboard Support
Key | Description |
---|---|
ArrowRight | Moves focus to the next star, increasing the rating value based on the `allowHalf` property. |
ArrowLeft | Moves focus to the previous star, decreasing the rating value based on the `allowHalf` property. |
Enter | Selects the focused star in the rating group. |