Gauge - headless

createGauge drives styled SvGauge plus a custom SVG meter sharing the same arc / needle geometry.

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 gauge core of the SvGrid UI kit in Svelte 5. A gauge is a display control, so createGauge is a light core: value, range and bands in, computed arc and needle geometry out, plus rootProps with role meter and aria-valuenow. One core drives the styled radial SvGauge and a custom linear meter drawn in SVG from the same geometry, both bound to a single value.

Headless gauge - the same headless-first split as the grid. A gauge is a display control, so createGauge is a light core: value in, computed arc/needle geometry out, plus a rootProps() with role="meter" + aria-valuenow. One core drives the styled radial <SvGauge> AND a custom linear meter, both bound to a single value.

Imports, features and API used

Imports: @svgrid/grid

Frequently asked questions

What geometry does the core compute?

The normalized fraction of the value within min and max, the arc angles for the sweep, the angle of the needle and the start and end of each band, so a render draws paths without redoing the math.

How can a linear meter use radial geometry?

It uses the fraction and the band boundaries and ignores the angles, mapping them to x positions on a bar.

What does rootProps add?

role meter with aria-valuemin, aria-valuemax and aria-valuenow, plus an accessible label, so the value is announced regardless of how it is drawn.

Related documentation

Related articles

Source code (282-headless-gauge.svelte)

<script lang="ts">
  /**
   * Headless gauge - the same headless-first split as the grid. A gauge is a display
   * control, so `createGauge` is a light core: value in, computed arc/needle geometry
   * out, plus a `rootProps()` with role="meter" + aria-valuenow. One core drives the
   * styled radial <SvGauge> AND a custom linear meter, both bound to a single value.
   */
  import { SvGauge, createGauge } from '@svgrid/grid'

  let value = $state(64)

  // The headless core - identical geometry + ARIA, our own SVG/DOM.
  const g = createGauge({
    value: () => value,
    min: () => 0,
    max: () => 100,
    sweep: () => 180,
    size: () => 160,
    thickness: () => 12,
    ariaLabel: () => 'Load',
  })
  const size = 160
</script>

<div class="wrap">
  <header>
    <h2>Headless gauge</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>createGauge</code>
      computes the arc geometry and the <code>role="meter"</code> ARIA; both renders below read it.
    </p>
  </header>

  <div class="cols">
    <section>
      <h3>Styled <code>&lt;SvGauge&gt;</code></h3>
      <SvGauge {value} min={0} max={100} unit="%" ariaLabel="Load" />
    </section>

    <section>
      <h3>Your markup, same core</h3>
      <div class="meter" {...g.rootProps()}>
        <svg viewBox={`0 0 ${size} ${size / 2 + 16}`} width={size} height={size / 2 + 16}>
          <path d={g.arcPath(0, 100, g.r)} fill="none" stroke="var(--sg-border, #e2e8f0)" stroke-width="12" stroke-linecap="round" />
          <path d={g.arcPath(0, g.clamped, g.r)} fill="none" stroke="var(--sg-accent, #4f46e5)" stroke-width="12" stroke-linecap="round" />
          <line x1={g.cx} y1={g.cy} x2={g.needleEnd.x} y2={g.needleEnd.y} stroke="var(--sg-fg, #0f172a)" stroke-width="3" stroke-linecap="round" />
          <circle cx={g.cx} cy={g.cy} r="4" fill="var(--sg-fg, #0f172a)" />
        </svg>
        <div class="meter__val">{Math.round(g.clamped)}%</div>
      </div>
      <p class="hint">Same <code>arcPath</code> / needle math and meter ARIA - only the SVG is different.</p>
    </section>
  </div>

  <aside class="readout">
    <h3>Value</h3>
    <input type="range" min="0" max="100" bind:value aria-label="Set gauge value" />
    <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 gauge render - nothing shared with SvGauge but the core geometry. */
  .meter { position: relative; display: inline-flex; flex-direction: column; align-items: center; color: var(--sg-fg, #0f172a); }
  .meter svg { display: block; }
  .meter__val { margin-top: 2px; font-size: 20px; font-weight: 750; font-variant-numeric: tabular-nums; }
  .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); }
  .readout input { width: 220px; accent-color: var(--sg-accent, #4f46e5); }
  .tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
  .tag { padding: 3px 10px; border-radius: 6px; font-size: 13px; background: var(--sg-row-hover-bg, #f1f5f9); font-variant-numeric: tabular-nums; }
</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.