0.1.0

Knob

Knobs let users make compact, rotary adjustments to a numeric value. They are particularly useful for controls such as gain, pan, and effect parameters.

Try it

Usage

Set min, max, and step to define the range. The knob’s visible arc spans 300°, leaving a gap that clearly separates the range endpoints.

import { RuiKnob } from '@ecopages/radiant-ui/knob';
import { RuiField } from '@ecopages/radiant-ui/field';
import { RuiLabel } from '@ecopages/radiant-ui/label';
 
<RuiField name="gain">
  <RuiLabel>Gain</RuiLabel>
  <RuiKnob value={0} min={-24} max={24} step={0.5} valueTemplate="{value} dB" />
</RuiField>

Custom markup

<rui-knob> coordinates any light-DOM tree that matches its query contract. The RuiKnob helper stamps these targets; it is not required.

import '@ecopages/radiant-ui/knob';
 
<rui-knob value={50} min={0} max={100} label="Gain">
  <div class="rui-knob" data-ref="root">
    <span class="rui-knob__label" data-ref="label">Gain</span>
    <button type="button" class="rui-knob__control" data-ref="control" data-knob-control role="slider">
      <svg class="rui-knob__svg" viewBox="0 0 100 100" aria-hidden="true">
        <circle class="rui-knob__track" data-ref="track" cx="50" cy="50" fill="none" />
        <circle class="rui-knob__progress" data-ref="progress" cx="50" cy="50" fill="none" />
      </svg>
      <span class="rui-knob__value" data-ref="centerValue" aria-hidden="true">50</span>
    </button>
  </div>
</rui-knob>

BEM classes are presentation-only. The host owns control aria-*, ring geometry, and readout text. The RuiKnob view seeds those so SSR matches the hydrated control. Set name on the host to submit with a native form.

Use valuePosition="below" to place the readout under the knob, or showValue={false} when the value is shown elsewhere. valueTemplate replaces the first {value} token with the formatted value.

step is the snap interval for the stored value. valuePrecision only formats the readout, aria-valuetext, and related display text. It defaults to the decimal places in step, so 0.1 + 0.2 can still be stored as a binary float while the UI shows 0.3. Round or transform committed values in application code when you need a canonical number.

<RuiKnob min={0} max={1} step={0.001} value={0.343} valuePrecision={2} />

Set --rui-knob-size for CSS-controlled sizing. The numeric size prop overrides this variable when a specific pixel diameter is needed.

rui-knob[data-size='large'] {
  --rui-knob-size: 5rem;
}

Theming

The knob consumes semantic roles by default. Override its public custom properties to adapt the ring without replacing component CSS. Unfilled track contrast is shared with sliders (--rui-track-mix, --rui-track-fill, --rui-track-color).

PartDefault roleCustom property
Track--rui-track-fill--rui-knob-track-color
Progressprimary--rui-knob-value-color
Readouton-surface--rui-knob-text-color
Diameter3rem--rui-knob-size
Focus ringfocus-ring—

Set the mix or a solid color on an ancestor to theme slider and knob together:

:root {
  --rui-track-mix: 28%;
}
 
.panel {
  --rui-track-color: var(--surface-container-high);
}

API

RuiKnob is a custom element (<rui-knob>) with a JSX helper of the same name.

Attributes

AttributeTypeDefaultDescription
valuenumber50Selected value.
minnumber0Range minimum.
maxnumber100Range maximum.
stepnumber1Pointer and keyboard snap interval.
value-precisionnumberdecimal places in stepMaximum fraction digits in the value readout.
disabledbooleanfalseDisables interaction.
read-onlybooleanfalseBlocks changes but allows focus.
labelstring''Visible and accessible name.
namestring''Form field name on this host.
sizenumber—Explicit visible SVG diameter in pixels; overrides --rui-knob-size.
stroke-widthnumber14Progress ring width in view-box units.
show-valuebooleantrueShows the value inside the ring.
value-positioncenter · belowcenterPlaces the value inside or below the knob.
value-templatestring'{value}'Readout template.

Light-DOM contract

TargetRequiredHost writesAuthor owns
[data-ref="root"]yesrui-knob--value-below, --rui-knob-size on hostroot wrapper
[data-ref="control"]yesaria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext, aria-label, aria-readonly, disabledcontrol button; also stamp data-knob-control for fields. View seeds aria-valuenow
[data-ref="track"]yesr, stroke-width, stroke-dasharraySVG track circle; view seeds geometry
[data-ref="progress"]yesr, stroke-width, stroke-dasharraySVG progress circle; view seeds geometry
[data-ref="label"]nohidden, textContentvisible label
[data-ref="centerValue"]notextContent, hiddenin-ring readout; view seeds the formatted value
[data-ref="belowValue"]notextContent, hiddenbelow-ring readout; view seeds the formatted value

Do not set control tabindex. Nested hosts: none. The RuiKnob view seeds readout text, ring geometry, and aria-valuenow so the first paint matches the hydrated control.

Events

EventDetailDescription
rui-change{ value: number }Emitted as pointer or keyboard interaction changes the value.

View helpers

ComponentTarget stampedNotes
RuiKnobfull [data-ref] tree under [data-ref="root"]Stamps control, ring SVG, and readouts. Seeds readout text and ring geometry for SSR.

CSS classes

ClassDescription
.rui-knobRoot; wraps the label and control.
.rui-knob--value-belowRoot when the value is below the control.
.rui-knob__labelOptional visible label.
.rui-knob__controlFocusable slider and pointer target.
.rui-knob__svgSVG ring.
.rui-knob__trackUnfilled range arc.
.rui-knob__progressFilled range arc.
.rui-knob__valueValue readout inside the ring.

Accessibility

  • The interactive surface exposes role="slider" with its current, minimum, and maximum values.
  • Arrow keys adjust by one step; Page Up/Down adjust by ten steps; Home and End move to the range endpoints.
  • The pointer target is at least 44 × 44 pixels, including when a smaller visible SVG size is requested.