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 (defaultaxis="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)
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; nooas-changefollows);oas-reset: after double-click / Ctrl+click /reset()(detail: { value, defaultValue }).
Touch & edge cases
- Pointer Events unify mouse/touch (
touch-action: noneclaims the drag, primary pointer is exclusive,pointercancelcancels 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 programmaticreset()during a drag first cancels the drag, then resets; wheelresponds to vertical scrolling (deltaY) only; horizontal trackpad panning does not adjust the value;- A throwing
formatValuefunction degrades to theformattemplate /unitsuffix / plain number (rendering and gestures keep working); - External
valuewrites 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
| Attribute | Description | Type | Default |
|---|---|---|---|
axis | Linear drag axis: vertical (default, up increases, 150px covers the full range) / horizontal (right increases); acts as the preferred axis for interaction=auto | string | — |
default-value | Reset 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 | — |
disabled | Disabled: unfocusable (tabindex removed), dragging/keyboard/wheel/reset all blocked, aria-disabled="true", dimmed (opacity .6) | boolean | — |
end-angle | Sweep 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 | — | — |
format | Value 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 function | string | — |
include-arc | Show 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 | — |
indicator | Indicator style: line (radial pointer line, default) / dot (dot near the rim); invalid values fall back to line | string | — |
interaction | Gesture 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 exclusive | string | auto |
label | Knob accessible name (aria-label; renders no visible label — visible labels belong to oas-form-item); falls back to the built-in "Knob" i18n copy | string | — |
large-step | Keyboard large step (Shift+arrows / PageUp / PageDown); defaults to 10 × step (span / 10 when continuous) | string | — |
max | Range upper bound (default 100) | string | 100 |
min | Range lower bound (default 0); invalid values or min > max are swapped as a fallback | string | 0 |
readonly | Readonly (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 | — |
reverse | Reverse: 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 direction | boolean | — |
show-value | Display the current value below the knob (formatted via the formatValue function / format template / unit suffix, same source as aria-valuetext) | boolean | — |
size | Size 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 minimum | string | medium |
start-angle | Sweep start angle (0° = 12 o'clock, clockwise; default 225); end-angle <= start-angle is normalized to a minimal 1° sweep | — | — |
start-point | Value 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 | — |
step | Snap step (default 1); 0 or negative means continuous (no snapping) | string | 1 |
unit | Value suffix (e.g. Hz/%/dB): feeds the show-value text and aria-valuetext; lower priority than the format template and the formatValue function | string | — |
value | Current 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) | number | 0 |
wheel | Wheel 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 enabled | boolean | — |
Property (JS property; see each row for attribute reflection)
| Property | Description | Type | Default |
|---|---|---|---|
defaultValue | Reset target: reads the default-value attribute (null when unset = falls back to min); assigning a number reflects to the attribute, assigning null clears it | number | null | — |
formatValue | Value 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 | — |
value | Current 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
| Event | Description |
|---|---|
oas-change | Emitted on release commit / each full keyboard or wheel confirmation, detail: { value }; not emitted for cancels (Esc/pointercancel) or zero-change releases |
oas-drag-end | Emitted 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-start | Emitted after the pointer presses and moves effectively (≥ threshold), detail: { value } (start value); not emitted for a tap without movement |
oas-input | Emitted live on each value change while dragging / wheel / keyboard, detail: { value } (snapped current value) |
oas-reset | Emitted after a reset gesture (double-click / Ctrl+click / reset()), detail: { value, defaultValue } |
CSS Variables
| CSS Variable | Description | Default |
|---|---|---|
--oas-knob-body | Knob body color | var(--oas-color-bg-elevated) |
--oas-knob-fill | Value arc color (when include-arc is on) | var(--oas-color-primary) |
--oas-knob-indicator | Indicator (pointer line / dot) color | var(--oas-color-text-secondary-strong) |
--oas-knob-size | Knob diameter (carrier of the size tiers sm=40px / md=56px / lg=72px; override for a custom diameter) | 56px |
--oas-knob-track | Track arc color (full-range sweep arc) | var(--oas-color-border) |
--oas-knob-value-color | show-value text color | var(--oas-color-text-secondary) |