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-partattribute 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 } from 'lucide-vue-next'
</script>
<template>
<RatingGroup.Root :count="5" :default-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" fill="current" />
<StarIcon 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 } from 'lucide-vue-next'
</script>
<template>
<RatingGroup.Root :count="5" :default-value="3.5" 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" fill="current" />
<StarIcon 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 } from 'lucide-vue-next'
</script>
<template>
<RatingGroup.Root :count="5" :default-value="2">
<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" fill="current" />
<StarIcon 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 { StarIcon } from 'lucide-vue-next'
import { ref } from 'vue'
const value = ref(0)
</script>
<template>
<RatingGroup.Root :count="5" v-model="value">
<RatingGroup.Label>Label {{ value }}</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" fill="current" />
<StarIcon 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 } from 'lucide-vue-next'
</script>
<template>
<RatingGroup.Root :count="5" :default-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" fill="current" />
<StarIcon 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="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 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 } from 'lucide-vue-next'
</script>
<template>
<RatingGroup.Root :count="5" :default-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" fill="current" />
<StarIcon 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 } from 'lucide-vue-next'
</script>
<template>
<RatingGroup.Root name="my-rating" :count="5" :default-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" fill="current" />
<StarIcon 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 } from 'lucide-vue-next'
</script>
<template>
<Field.Root>
<RatingGroup.Root :count="5" :default-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" fill="current" />
<StarIcon 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>
Using the Root Provider
The RootProvider component provides a context for the rating-group. It accepts the value of the useRating-group
hook. You can leverage it to access the component state and methods from outside the rating-group.
import { RatingGroup, useRatingGroup } from '@ark-ui/react/rating-group'
import { StarIcon } from 'lucide-react'
export const RootProvider = () => {
const ratingGroup = useRatingGroup({ count: 5, defaultValue: 3 })
return (
<>
<button onClick={() => ratingGroup.clearValue()}>Clear</button>
<RatingGroup.RootProvider value={ratingGroup}>
<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.RootProvider>
</>
)
}
import { RatingGroup, useRatingGroup } from '@ark-ui/solid/rating-group'
import { StarIcon } from 'lucide-solid'
import { Index, Show } from 'solid-js'
export const RootProvider = () => {
const ratingGroup = useRatingGroup({ count: 5, value: 3 })
return (
<>
<button onClick={() => ratingGroup().clearValue()}>Clear</button>
<RatingGroup.RootProvider value={ratingGroup}>
<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.RootProvider>
</>
)
}
<script setup lang="ts">
import { RatingGroup, useRatingGroup } from '@ark-ui/vue/rating-group'
import { StarIcon } from 'lucide-vue-next'
const ratingGroup = useRatingGroup({ count: 5, defaultValue: 3 })
</script>
<template>
<button @click="ratingGroup.clearValue()">Clear</button>
<RatingGroup.RootProvider :value="ratingGroup">
<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" fill="current" />
<StarIcon v-else />
</RatingGroup.ItemContext>
</RatingGroup.Item>
</RatingGroup.Context>
<RatingGroup.HiddenInput />
</RatingGroup.Control>
</RatingGroup.RootProvider>
</template>
If you're using the
RootProvidercomponent, you don't need to use theRootcomponent.
API Reference
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. |