Skip to content

Knob ​

A rotary input control that encodes a continuous/discrete value as an angle around a circle — built for parameter-dense panels (audio/industrial/3D): compact circular form, relative dragging (no jumps), and bipolar zero-point.

Basic Usage ​

Basic usage

The knob is a compact circular input: drag or use the keyboard to change the value; the range maps linearly onto the sweep (by default 225° → 495°, a 270° sweep with a gap at the bottom, like the stop of a physical panel knob). The pointer gesture is relative: pressing writes nothing and the value follows the hand (no jumping to the click point, unlike a linear slider).

Range & Step ​

min / max / step (step=0 means continuous)

min/max define the range (invalid values or min > max are swapped as a fallback); step snaps the value, 0 or a negative value means continuous (no snapping). Out-of-range values are clamped.

Sweep angles (start-angle / end-angle) ​

Custom sweeps: 300° near-full circle / half-dial

start-angle/end-angle customize the sweep (0° = 12 o'clock, clockwise; defaults 225 → 495, a 270° sweep with a bottom gap). end-angle may exceed 360 (e.g. 510 = start at 210°, sweep 300° clockwise); end-angle <= start-angle is normalized to a minimal 1° sweep (divide-by-zero guard).

Three gesture models (interaction) ​

cursor circular bearing / axis linear drag / auto resolving (default)

  • cursor: the pointer's bearing angle around the shaft drives the value incrementally (clockwise increases) — the signature knob gesture;
  • axis: linear drag (default axis="vertical", up increases, 150px covers the full range; axis="horizontal" switches to rightward increase) — the common convention of pro-audio plugins;
  • auto (default): resolves within the first few pixels — dominant direction along the preferred axis goes linear, perpendicular goes circular; resolved once and locked for the rest of the gesture (switching mid-drag would jump the value).

All gestures are relative: pressing writes nothing, a tap without movement is a no-op; the primary pointer is exclusive; holding Shift drags at 0.2× for fine adjustment.

Reset (double-click / Ctrl+click / default-value) ​

Double-click or Ctrl+click resets to default-value (falls back to min)

Double-click or Ctrl/Cmd + click resets to default-value; without it the knob falls back to min. Resetting emits oas-reset (detail: { value, defaultValue }) and does not emit oas-change (a reset is not a value confirmation); readonly/disabled block all reset gestures. For programmatic reset use the el.reset() method.

Value arc & bipolar zero (include-arc + start-point) ​

include-arc value arc; start-point at center = bipolar (pan/gain)

include-arc shows the value arc (filled from start-point, falling back to min, up to the current value); setting start-point to the center value makes the arc bipolar — values above the origin extend to one side, below to the other (pan/gain centered at 12 o'clock). The arc hides when the value equals the origin.

Indicator (indicator) ​

line pointer (default) / dot

indicator="line" draws a radial pointer line (default); indicator="dot" draws a dot near the rim.

Value display (show-value / unit / format) ​

show-value text + unit suffix / format template

show-value displays the current value below the knob; unit appends a suffix (Hz/%/dB); format is a template string (${value} placeholder). The formatted output also feeds aria-valuetext (screen readers announce what is visible); for richer formatting assign the el.formatValue = (value) => string function property (highest priority, set null to clear).

Size ​

size tiers (sm / md / lg), custom diameter via CSS variable

size maps to diameters sm=40px / md=56px (default) / lg=72px (also accepts the small/medium/large vocabulary with config-provider injection); invalid values fall back to md. Custom diameters override the CSS variable --oas-knob-size. On coarse-pointer devices the sm tier is raised to the 44px touch-target minimum.

Mouse wheel (wheel) ​

wheel adjusts on hover (off by default)

With wheel enabled, scrolling over the knob adjusts the value (scroll up increases; one notch = step, or 1/50 of the range when continuous). Modifier keys do not change the step; for fine adjustment use Shift+drag (0.2×). Off by default (page scrolling stays untouched).

Reverse (reverse) ​

reverse mirrors the value sweep and the circular gesture (min starts at end-angle)

reverse flips the sweep direction (min → end-angle, max → start-angle) and mirrors the circular gesture and the left/right arrow keys; RTL writing direction (dir="rtl") mirrors automatically and XORs with reverse.

Keyboard ​

Keyboard large steps (large-step)

With the knob focused: arrow keys step by step (up/down always increase/decrease; left/right follow the mirror), Shift + arrows and PageUp/PageDown step by large-step (default 10 × step), Home/End jump to the extremes. Every keypress is a full commit (emits oas-input + oas-change); keys at the extremes stay silent (no events).

Readonly & disabled ​

readonly (not dimmed) / disabled (dimmed, unfocusable)

readonly and disabled are independent (same contract as slider): a readonly knob stays focusable and its value still participates in host collection, but dragging/keyboard/wheel/reset are all intercepted and it is not visually dimmed; a disabled knob is unfocusable (tabindex removed), sets aria-disabled="true" and is dimmed.

Accessible name (label) ​

label accessible name (no visible label; visible labels belong to oas-form-item)

Show aria-label

label becomes the knob's aria-label (falls back to the built-in "Knob" copy). The knob renders no visible label of its own — use oas-form-item's label for a visible label (clicking it focuses the knob).

Events ​

Drag / keyboard / reset events (oas-input live, oas-change commit, oas-drag-* boundaries, oas-reset)

Event contract (every detail carries value):

  • oas-input: each value change while dragging / wheel / keyboard (live);
  • oas-change: commit on release / each full keyboard or wheel confirmation (not emitted for cancels or zero-change releases);
  • oas-drag-start: the pointer pressed and moved effectively (no event for a tap without movement);
  • oas-drag-end: release (cancelled: false) or cancel (cancelled: true; no oas-change follows);
  • oas-reset: after double-click / Ctrl+click / reset() (detail: { value, defaultValue }).

Touch & edge cases ​

  • Pointer Events unify mouse/touch (touch-action: none claims the drag, primary pointer is exclusive, pointercancel cancels and rolls back);
  • Esc during a drag cancels: rolls back to the start value with zero commits (no oas-change);
  • Reset gestures during a drag (double-click / Ctrl+click) are ignored; losing pointer capture to another element (lostpointercapture) cancels and rolls back; a programmatic reset() during a drag first cancels the drag, then resets;
  • wheel responds to vertical scrolling (deltaY) only; horizontal trackpad panning does not adjust the value;
  • A throwing formatValue function degrades to the format template / unit suffix / plain number (rendering and gestures keep working);
  • External value writes during a drag: the display keeps the dragging value; on release a committed drag wins, a zero-change release keeps the external value;
  • Touch assist technologies may not fully support custom slider gestures (W3C ARIA APG note) — test with real AT before shipping on mobile.

API ​

oas-knob ​

Attributes ​

AttributeDescriptionTypeDefault
axisLinear drag axis: vertical (default, up increases, 150px covers the full range) / horizontal (right increases); acts as the preferred axis for interaction=autostring—
default-valueReset target: double-click / Ctrl+click / reset() resets to this value (snapped + clamped); falls back to min; resetting emits oas-reset (not oas-change)number | null—
disabledDisabled: unfocusable (tabindex removed), dragging/keyboard/wheel/reset all blocked, aria-disabled="true", dimmed (opacity .6)boolean—
end-angleSweep end angle (default 495, a 270° sweep with a bottom gap); may exceed 360 (e.g. 510 = start at 210°, 300° clockwise); sweep is capped at 360——
formatValue format template: the ${value} placeholder is replaced with the current value (e.g. "${value} dB"); output feeds both the value text and aria-valuetext; lower priority than the formatValue functionstring—
include-arcShow the value arc: filled from start-point (falling back to min) to the current value; hidden when the value equals the origin (prevents a round linecap dot)boolean—
indicatorIndicator style: line (radial pointer line, default) / dot (dot near the rim); invalid values fall back to linestring—
interactionGesture model: cursor (pointer bearing drives the value incrementally, clockwise increases) / axis (linear drag) / auto (default — resolved within the first few pixels: dominant direction along the preferred axis goes linear, perpendicular goes circular, resolved once and locked for the gesture); relative semantics: pressing writes nothing, a tap without movement is a no-op, primary pointer is exclusivestringauto
labelKnob accessible name (aria-label; renders no visible label — visible labels belong to oas-form-item); falls back to the built-in "Knob" i18n copystring—
large-stepKeyboard large step (Shift+arrows / PageUp / PageDown); defaults to 10 × step (span / 10 when continuous)string—
maxRange upper bound (default 100)string100
minRange lower bound (default 0); invalid values or min > max are swapped as a fallbackstring0
readonlyReadonly (independent of disabled): stays focusable and the value still participates in host collection, but dragging/keyboard/wheel/reset are all intercepted; not visually dimmed; sets aria-readonly="true"boolean—
reverseReverse: mirrors the value sweep (min → end-angle, max → start-angle) and mirrors the circular gesture and the left/right arrow keys; XORs with the RTL writing directionboolean—
show-valueDisplay the current value below the knob (formatted via the formatValue function / format template / unit suffix, same source as aria-valuetext)boolean—
sizeSize tier: sm (40px) / md (56px, default) / lg (72px); also accepts the small / medium / large vocabulary (config-provider injectable); invalid values fall back to md; custom diameters override the CSS variable --oas-knob-size; on touch devices the sm tier is raised to the 44px touch-target minimumstringmedium
start-angleSweep start angle (0° = 12 o'clock, clockwise; default 225); end-angle <= start-angle is normalized to a minimal 1° sweep——
start-pointValue arc origin (missing/invalid falls back to min, clamped to the range); setting it to the center value makes the arc bipolar — values above the origin extend to one side, below to the other (pan/gain zero point)string—
stepSnap step (default 1); 0 or negative means continuous (no snapping)string1
unitValue suffix (e.g. Hz/%/dB): feeds the show-value text and aria-valuetext; lower priority than the format template and the formatValue functionstring—
valueCurrent value (controlled): out-of-range values are clamped to [min, max] and snapped by step; written back to the attribute after interaction (hosts can read the latest value directly from the attribute)number0
wheelWheel adjustment on hover (off by default): scroll up increases, down decreases, one notch = step (span/50 when continuous); modifier keys do not change the step (fine adjustment via Shift+drag); preventDefault stops page scrolling while enabledboolean—

Property (JS property; see each row for attribute reflection) ​

PropertyDescriptionTypeDefault
defaultValueReset target: reads the default-value attribute (null when unset = falls back to min); assigning a number reflects to the attribute, assigning null clears itnumber | null—
formatValueValue formatter: el.formatValue = (value) => string, output feeds both the show-value text and aria-valuetext; higher priority than the format attribute and unit suffix; set null to clear((value: number) => string | number | null | undefined) | null—
valueCurrent value: reads back the clamped + snapped number (the live dragging value mid-drag); writing is controlled-assign-immediate (writes the attribute and force-syncs, no events)number—

Events ​

EventDescription
oas-changeEmitted on release commit / each full keyboard or wheel confirmation, detail: { value }; not emitted for cancels (Esc/pointercancel) or zero-change releases
oas-drag-endEmitted when the drag ends, detail: { value, cancelled }; cancelled=true marks the cancel path (rolled back to the start value, no oas-change follows)
oas-drag-startEmitted after the pointer presses and moves effectively (≥ threshold), detail: { value } (start value); not emitted for a tap without movement
oas-inputEmitted live on each value change while dragging / wheel / keyboard, detail: { value } (snapped current value)
oas-resetEmitted after a reset gesture (double-click / Ctrl+click / reset()), detail: { value, defaultValue }

CSS Variables ​

CSS VariableDescriptionDefault
--oas-knob-bodyKnob body colorvar(--oas-color-bg-elevated)
--oas-knob-fillValue arc color (when include-arc is on)var(--oas-color-primary)
--oas-knob-indicatorIndicator (pointer line / dot) colorvar(--oas-color-text-secondary-strong)
--oas-knob-sizeKnob diameter (carrier of the size tiers sm=40px / md=56px / lg=72px; override for a custom diameter)56px
--oas-knob-trackTrack arc color (full-range sweep arc)var(--oas-color-border)
--oas-knob-value-colorshow-value text colorvar(--oas-color-text-secondary)

Released under the MIT OR Apache-2.0 License.