Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelogApp ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbarAnchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree ViewAccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual ListActivity HeatmapAnimated NumberBadgeBar ListChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveformBlockquoteCode BlockCode GroupGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriterButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerField ListFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree SelectAlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip
ARC UIARC Radiant Components
v4.9DocsComponentsTokensSynthesizer
Getting StartedFrameworksServer RenderingDesign TokensThemingTheme SynthesizerTypographyUtilitiesAll ComponentsAccessibilityBrowser SupportChangelogContributingStatsApp ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbarAnchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree ViewAccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual ListActivity HeatmapAnimated NumberBadgeBar ListChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveformBlockquoteCode BlockCode GroupGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriterButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerField ListFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree SelectAlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip

Knob

Rotary knob input for continuous parameters, with a glowing 270-degree arc fill, vertical-drag interaction, magnetic detents, and a monospace value readout.

ComponentsKnob
inputinteractive
<arc-knob>

Overview

Knob is the input a slider cannot be: a rotary control that packs a full parameter range into a compact circular footprint, so a rack of channel strips or a synth panel can put a dozen of them side by side. A 270-degree arc track fills from `min` to the current value, a glowing indicator line marks the position, and the readout below renders in the monospace role with tabular digits so it never shifts width while turning. Interaction follows audio-software convention. Dragging vertically turns the knob (the full range covers about 150 pixels of travel), and holding Shift slows the drag to a tenth for fine adjustment. The mouse wheel and arrow keys step by `step`, Page Up and Page Down jump by ten steps, and Home and End go straight to the rails. The optional `detents` prop names snap values (an array from script, or a comma-separated attribute): each renders as a tick mark around the dial, and a drag snaps magnetically when it lands close, as with the centre detent on a pan knob or unity on a gain knob. Keyboard and wheel stepping ignore detents, so precise entry is never fought. Knob follows the v3 edit/commit contract: `arc-input` fires continuously while the knob turns and `arc-change` fires once when the turn commits, so a live preview and an expensive save can listen separately. The component participates in forms through ElementInternals, submitting its value under `name`, and the dial is a keyboard-operable `role="slider"` with the full ARIA value set. `format` shapes both the visible readout and the accessible value text, so a screen reader hears "440 Hz" rather than a bare number.

Guidelines

When to use

  • Use Knob where horizontal space is scarce and controls sit in columns: mixer strips, effect panels, tool palettes
  • Provide a `label`; a bare dial gives no clue what parameter it turns
  • Set `format` to include the unit, so both the readout and screen readers announce "440 Hz" rather than "440"
  • Put a detent at the neutral position of a bipolar parameter: 0 on a pan knob, unity on a gain knob
  • Listen to `arc-input` for live audible or visible preview and to `arc-change` for persisting the committed value

When not to use

  • Do not use Knob for a wide-layout single value where a slider fits. A slider shows its whole range at a size a knob cannot
  • Do not use Knob for exact numeric entry. Pair it with or replace it by Number Input when users need to type a value
  • Do not scatter detents densely across the range; a magnet every few units makes smooth dragging impossible
  • Do not rely on the dial alone to convey the value. The readout below it is part of the control, so leave it visible

Features

  • A 270-degree SVG arc track with an accent fill from `min` to the current value and a glowing indicator line
  • Synth-style vertical drag with pointer capture; Shift slows the drag to a tenth for fine adjustment
  • Mouse wheel and arrow keys step by `step`, Page Up/Down by ten steps, Home/End to the rails
  • Optional `detents` render tick marks and snap the drag magnetically to named values
  • `format` callback shapes the readout and the ARIA value text, e.g. adding a unit suffix
  • The v3 event contract: `arc-input` continuously while turning, `arc-change` once on commit
  • Form participation via ElementInternals. The value submits under `name` and restores on `form.reset()`
  • Keyboard-operable `role="slider"` dial with `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, and `aria-valuetext`

Preview

Usage

Knob is in the media domain group, so it is absent from the default @arclux/arc-ui barrel. Import it from@arclux/arc-ui/media, or from its own subpath@arclux/arc-ui/knob. Everything else (the element, the CSS, the framework wrappers, the support it gets) is unchanged.

This component requires JavaScript. No pure HTML/CSS version is available. Use the Web Component directly or a framework wrapper.

<script type="module" src="@arclux/arc-ui"></script>

<!-- Basic labeled knob -->
<arc-knob label="Volume" value="75" min="0" max="100"></arc-knob>

<!-- Bipolar pan knob with a centre detent -->
<arc-knob label="Pan" value="0" min="-50" max="50" detents="0"></arc-knob>

<!-- Filter cutoff with a formatted readout -->
<arc-knob id="cutoff" label="Cutoff" value="8000" min="200" max="16000" step="100"></arc-knob>

<!-- Disabled state -->
<arc-knob label="Locked" value="30" disabled></arc-knob>

<script>
  const cutoff = document.getElementById('cutoff');
  cutoff.format = (v) => v >= 1000 ? (v / 1000).toFixed(1) + ' kHz' : v + ' Hz';

  // Real-time preview while turning
  cutoff.addEventListener('arc-input', (e) => {
    filterNode.frequency.value = e.detail.value;
  });

  // Commit final value on release
  cutoff.addEventListener('arc-change', (e) => {
    savePatch({ cutoff: e.detail.value });
  });
</script>

API

SWEEPnumber270
Degrees of arc the knob sweeps; the remaining 90 stay open at the bottom.
DRAG_THROWnumber150
Pixels of vertical drag that cover the full range.
DETENT_WINDOWnumber0.025
Fraction of the range within which a drag snaps to a detent.
valuenumber0
Current knob value. Reflected as an attribute and updated on user interaction.
namestring''
disabledbooleanfalse
Disables interaction, reducing opacity and blocking pointer events.
labelstring''
Label text displayed above the knob in the label typography role.
formatFunctionundefined
(value) => string shaping the readout and the accessible value text, for example adding a unit suffix. Defaults to the plain number.
minnumber0
Minimum allowed value at the start of the arc sweep.
maxnumber100
Maximum allowed value at the end of the arc sweep.
stepnumber1
Increment granularity. The value snaps to multiples of this number.
detentsnumber[][]
Snap values, as an array from script or a comma-separated attribute (for example "0,50,100"). While dragging, the value snaps magnetically to a detent within 2.5% of the range, and each detent renders as a tick mark around the dial. Keyboard and wheel stepping ignore detents.
readonlybooleanfalse
Prevents dragging, wheel, and key changes while the dial stays focusable and the value still submits.
size'sm' | 'md' | 'lg''md'
Control size. md is the default; sm and lg scale the dial.
formAssociatedbooleantrue
propertiesobject{ // flag(), unlike `disabled`. The exclusion in props.js is specifically // about form-associated *platform* semantics: a `disabled` content // attribute that is merely present makes the element actually disabled // per the HTML spec, and formDisabledCallback assigns the property back, // so no converter can win. Neither of these is platform-mapped: // `required` is enforced by _computeValidity() below and `readonly` by // each component's own interaction handlers, so the stock converter buys // nothing here and costs the usual bug: `required="false"` read as true, // blocking submission of a form the author meant to leave optional. // Finding #48's shape, across all 26 form controls at once. required: flag(false), readonly: flag(false), }
Lit merges static properties up the prototype chain, so every consumer gets these without declaring them. required participates in constraint validation below; readonly reflects for styling and is enforced by each component's interaction handlers (the mixin can't know which gestures mutate state).
autoValidatesbooleantrue
Components that run their own constraint-validation logic (pattern checks, range checks) opt out of the automatic required sync by overriding this to false, and own the whole validity flag set instead.
form
validity
validationMessage
requiredbooleanfalse

Methods

checkValidity()boolean
Whether the control currently satisfies its constraints, per the native constraint-validation API. Fires invalid on the element when it does not, and reports nothing to the user.
reportValidity()boolean
As checkValidity(), but also shows the browser's validation message against the control when it fails.

Events

arc-inputdetail:{ value: number }
Fired continuously while the knob is turning: every drag movement, wheel notch, or key step. Use for real-time preview such as a filter cutoff or gain applied live.
arc-changedetail:{ value: number }
Fired once when a turn commits: on drag release, and after each discrete wheel or key step. Use for persisting the value or triggering an expensive operation.

See Also