Rating - headless

Styled SvRating (half stars) plus a custom segmented meter, one bound numeric value with live preview.

A live, editable Svelte 5 component example from the SvGrid gallery (Headless Editors). Read the SvGrid UI component docs for the full API.

About this example

The headless rating core of the SvGrid UI kit in Svelte 5. createRating is the state machine behind SvRating: hover preview, half steps, the keyboard with arrows, Home and End and the ARIA slider model, exposed as rootProps and starProps. The same core drives the styled half-star SvRating and a custom segmented meter, both bound to one numeric value with a live preview.

Headless rating - the SvGrid UI kit is headless-first + render-ready, just like the grid. createRating is the state machine behind <SvRating>: hover preview, half steps, keyboard (Arrows / Home / End) + ARIA slider, exposed as **prop-getters** you spread onto YOUR OWN markup. Below, the same core drives the styled component AND a totally custom render, bound to one value.

Imports, features and API used

Imports: @svgrid/grid

Frequently asked questions

How does hover preview work?

The core tracks a preview value from pointer moves that starProps reports and exposes it separately from the committed value, so both renders can show the hovered rating without changing the bound value until click.

How are half steps produced?

With allowHalf the core resolves the pointer position within a star to a half or full value; starProps(i) tells the render whether star i is empty, half or full.

What does the segmented meter show?

The same value as bars instead of stars, proving the core carries no star-specific assumptions.

Related documentation

Related articles

  • The Headless UI Pattern - Separating Logic from Presentation - How SvGrid splits the row-model pipeline from rendering, and when you actually want to reach past the default table and drive the headless layer directly.
  • SvGrid vs TanStack Table - A Deep Dive - A concrete architectural comparison of SvGrid and TanStack Table's Svelte adapter - how each handles reactivity, rendering, and feature composition, with code that shows exactly where they diverge.
  • SvGrid vs svelte-headless-table - A practical comparison of SvGrid and svelte-headless-table covering reactivity model, rendering approach, feature scope, and when each is the right choice for a Svelte 5 project.

Source code (275-headless-rating.svelte)

<script lang="ts">
  /**
   * Headless rating - the SvGrid UI kit is headless-first + render-ready, just
   * like the grid. `createRating` is the state machine behind <SvRating>: hover
   * preview, half steps, keyboard (Arrows / Home / End) + ARIA `slider`, exposed
   * as **prop-getters** you spread onto YOUR OWN markup. Below, the same core
   * drives the styled component AND a totally custom render, bound to one value.
   */
  import { SvRating, createRating } from '@svgrid/grid'

  let value = $state(3)

  // The headless core - identical behavior, our own DOM (a segmented meter here,
  // stars there), including half-step preview from a single pointer model.
  const r = createRating({
    value: () => value,
    onChange: (v) => (value = v),
    max: () => 5,
    allowHalf: () => true,
  })
</script>

<div class="wrap">
  <header>
    <h2>Headless rating</h2>
    <p>
      Every editor ships a headless core + a styled component - the same split as the grid
      (<code>createSvGrid</code> / <code>&lt;SvGrid&gt;</code>). Here <code>createRating</code>
      drives both renders below; hover preview, half steps, keyboard and ARIA come from the core, the markup is yours.
    </p>
  </header>

  <div class="cols">
    <section>
      <h3>Styled <code>&lt;SvRating&gt;</code></h3>
      <SvRating {value} allowHalf ariaLabel="Score" onChange={(v) => (value = v)} />
    </section>

    <section>
      <h3>Your markup, same core</h3>
      <div class="meter" {...r.rootProps()}>
        {#each Array(5) as _, i (i)}
          <button class="cell is-{r.fillOf(i)}" {...r.starProps(i)}></button>
        {/each}
      </div>
      <p class="hint">Hover for a live preview, arrow keys step by half - all from <code>createRating</code>.</p>
    </section>
  </div>

  <aside class="readout">
    <h3>Value</h3>
    <div class="tags"><span class="tag">{value}</span></div>
  </aside>
</div>

<style>
  .wrap { padding: 20px; max-width: 900px; display: flex; flex-direction: column; gap: 18px; }
  header h2 { margin: 0 0 4px; font-size: 20px; font-weight: 700; }
  header p { margin: 0; color: var(--sg-muted, #64748b); font-size: 13.5px; line-height: 1.55; max-width: 680px; }
  code { background: var(--sg-row-hover-bg, #eef2ff); padding: 1px 5px; border-radius: 5px; font-size: 12px; }
  .cols { display: flex; gap: 32px; flex-wrap: wrap; align-items: flex-start; }
  section h3 { margin: 0 0 10px; font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--sg-muted, #64748b); }
  /* Fully custom render - nothing shared with SvRating but the core. */
  .meter { display: inline-flex; gap: 6px; padding: 4px; border-radius: 8px; outline: none; }
  .meter:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--sg-accent, #4f46e5)); outline-offset: 2px; }
  .cell {
    width: 34px; height: 22px; padding: 0; border-radius: 5px; cursor: pointer;
    border: 1px solid var(--sg-border, #cbd5e1); background: var(--sg-input-bg, #fff); transition: background 0.1s;
  }
  .cell.is-full { background: var(--sg-accent, #4f46e5); border-color: transparent; }
  .cell.is-half { background: linear-gradient(90deg, var(--sg-accent, #4f46e5) 50%, var(--sg-input-bg, #fff) 50%); }
  .hint { margin: 10px 0 0; font-size: 12px; color: var(--sg-muted, #94a3b8); line-height: 1.5; }
  .readout h3 { margin: 0 0 8px; font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--sg-muted, #64748b); }
  .tags { display: flex; gap: 6px; flex-wrap: wrap; }
  .tag { padding: 3px 10px; border-radius: 6px; font-size: 13px; background: var(--sg-row-hover-bg, #f1f5f9); }
</style>

View this example on GitHub

More Headless Editors examples

  • Headless editors - Headless-first, like the grid: createListbox is the state machine behind SvListBox (roving focus, single/multi selection, keyboard, ARIA) exposed as prop-getters you spread onto YOUR own markup. One core drives both the styled SvListBox and a custom chip-cloud render, bound to one value.
  • Combobox - headless - createCombobox drives the styled SvComboBox and a custom filter-input render, both bound to one value with a readout.
  • Dropdown list - headless - createDropdownList drives SvDropDownList and a custom trigger+menu render, sharing one value.
  • Autocomplete - headless - createAutocomplete drives SvAutoComplete and a custom suggestion-list render over one text value.
  • Tags input - headless - createTagsInput drives SvTagsInput and a custom chip-cloud render, both editing one string array.