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).
| Part | Default role | Custom property |
|---|---|---|
| Track | --rui-track-fill | --rui-knob-track-color |
| Progress | primary | --rui-knob-value-color |
| Readout | on-surface | --rui-knob-text-color |
| Diameter | 3rem | --rui-knob-size |
| Focus ring | focus-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
| Attribute | Type | Default | Description |
|---|---|---|---|
value | number | 50 | Selected value. |
min | number | 0 | Range minimum. |
max | number | 100 | Range maximum. |
step | number | 1 | Pointer and keyboard snap interval. |
value-precision | number | decimal places in step | Maximum fraction digits in the value readout. |
disabled | boolean | false | Disables interaction. |
read-only | boolean | false | Blocks changes but allows focus. |
label | string | '' | Visible and accessible name. |
name | string | '' | Form field name on this host. |
size | number | — | Explicit visible SVG diameter in pixels; overrides --rui-knob-size. |
stroke-width | number | 14 | Progress ring width in view-box units. |
show-value | boolean | true | Shows the value inside the ring. |
value-position | center · below | center | Places the value inside or below the knob. |
value-template | string | '{value}' | Readout template. |
Light-DOM contract
| Target | Required | Host writes | Author owns |
|---|---|---|---|
[data-ref="root"] | yes | rui-knob--value-below, --rui-knob-size on host | root wrapper |
[data-ref="control"] | yes | aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext, aria-label, aria-readonly, disabled | control button; also stamp data-knob-control for fields. View seeds aria-valuenow |
[data-ref="track"] | yes | r, stroke-width, stroke-dasharray | SVG track circle; view seeds geometry |
[data-ref="progress"] | yes | r, stroke-width, stroke-dasharray | SVG progress circle; view seeds geometry |
[data-ref="label"] | no | hidden, textContent | visible label |
[data-ref="centerValue"] | no | textContent, hidden | in-ring readout; view seeds the formatted value |
[data-ref="belowValue"] | no | textContent, hidden | below-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
| Event | Detail | Description |
|---|---|---|
rui-change | { value: number } | Emitted as pointer or keyboard interaction changes the value. |
View helpers
| Component | Target stamped | Notes |
|---|---|---|
RuiKnob | full [data-ref] tree under [data-ref="root"] | Stamps control, ring SVG, and readouts. Seeds readout text and ring geometry for SSR. |
CSS classes
| Class | Description |
|---|---|
.rui-knob | Root; wraps the label and control. |
.rui-knob--value-below | Root when the value is below the control. |
.rui-knob__label | Optional visible label. |
.rui-knob__control | Focusable slider and pointer target. |
.rui-knob__svg | SVG ring. |
.rui-knob__track | Unfilled range arc. |
.rui-knob__progress | Filled range arc. |
.rui-knob__value | Value 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.