Rating Group
Selection & Inputimport { RatingGroup } from "bits-ui" Interactive half ratings
The items snippet exposes active, partial and inactive visual states while the bound value remains numeric
Release confidence
Half-step precision is enabled.
<script lang="ts">
import { RatingGroup } from 'bits-ui';
import { Star, StarHalf } from '@lucide/svelte';
let value = $state(3.5);
</script>
<div class="grid gap-3">
<div class="flex items-baseline justify-between gap-4">
<div>
<p class="text-sm font-medium">Release confidence</p>
<p class="muted mt-1 text-xs">Half-step precision is enabled.</p>
</div>
<output class="chip font-mono">{value.toFixed(1)} / 5</output>
</div>
<RatingGroup.Root
bind:value
max={5}
allowHalf
hoverPreview={true}
name="releaseConfidence"
aria-label="Release confidence"
aria-valuetext={(rating, maximum) => `${rating} out of ${maximum} confidence points`}
class="flex w-fit gap-1 rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-brand-400 focus-visible:ring-offset-2 focus-visible:ring-offset-ink-900"
>
{#snippet children({ items })}
{#each items as item (item.index)}
<RatingGroup.Item
index={item.index}
class="size-9 cursor-pointer p-1 text-ink-600 transition-colors hover:text-brand-400"
>
{#if item.state === 'active'}
<Star class="size-full text-brand-400" fill="currentColor" />
{:else if item.state === 'partial'}
<StarHalf class="size-full text-brand-400" fill="currentColor" />
{:else}
<Star class="size-full" />
{/if}
</RatingGroup.Item>
{/each}
{/snippet}
</RatingGroup.Root>
</div>
Read-only aggregate
Readonly preserves the same accessible value model without presenting an editable control
SvelteKit adapter
Aggregate from 1,284 verified projects
<script lang="ts">
import { RatingGroup } from 'bits-ui';
import { Star, StarHalf } from '@lucide/svelte';
const rating = 4.5;
</script>
<div class="card flex max-w-md items-center justify-between gap-6 p-4">
<div>
<p class="text-sm font-semibold text-ink-50">SvelteKit adapter</p>
<p class="muted mt-1 text-xs">Aggregate from 1,284 verified projects</p>
</div>
<div class="grid shrink-0 justify-items-end gap-1.5">
<RatingGroup.Root
value={rating}
max={5}
allowHalf
readonly
hoverPreview={false}
aria-label="Adapter rating"
aria-valuetext={`${rating} out of 5 stars`}
class="flex gap-0.5"
>
{#snippet children({ items })}
{#each items as item (item.index)}
<RatingGroup.Item
index={item.index}
class="size-5 cursor-default text-ink-600"
>
{#if item.state === 'active'}
<Star class="size-full text-accent-500" fill="currentColor" />
{:else if item.state === 'partial'}
<StarHalf class="size-full text-accent-500" fill="currentColor" />
{:else}
<Star class="size-full" />
{/if}
</RatingGroup.Item>
{/each}
{/snippet}
</RatingGroup.Root>
<span class="muted text-xs">{rating} average</span>
</div>
</div>
The bound value is always a number. allowHalf changes pointer and arrow-key precision to 0.5, and each item in the root snippet reports "inactive", "active" or "partial". Use those states for the artwork rather than recomputing fill from the value. max also controls how many item records are generated.
This component follows the ARIA slider pattern: the root is the single focus stop and items are presentational. Set name to render a hidden form input containing the numeric value; required marks that input required. aria-valuetext accepts either a string or a function of the current value and maximum.