# Knob

> A rotary control for dense layouts, drawn in SVG.

<ComponentPreview name="knob-demo" />

## Installation

```npm
npx shadcn-svelte@latest add @audiocn-svelte/knob
```

## Usage

```svelte
<script lang="ts">
	import { Knob } from "#lib/components/ui/knob/index.js";

	let gain = $state(0);
</script>

<Knob min={-24} max={24} origin={0} bind:value={gain} />
```

Drag the dial up to increase the value and down to decrease it. Set `dragDirection="circular"` to turn it by circling around the dial, clockwise to increase and anticlockwise to decrease.

| Action                                             | Result                                                                                  |
| -------------------------------------------------- | --------------------------------------------------------------------------------------- |
| Drag                                               | Turns the knob                                                                          |
| Shift+drag, Shift+wheel                            | Ten times finer, for precise adjustment. Press or release Shift mid-drag without a jump |
| Double-click the dial, Alt+click                   | Resets to `resetValue`                                                                  |
| Double-click the value or label, Enter on the dial | Type a value; Enter applies it, Escape cancels                                          |

The value keeps one width across the whole range, so the layout never moves while you turn it.

## Anatomy

```svelte
<Knob>
	<KnobDial>
		<KnobTrack />
		<KnobRange />
		<KnobPointer />
	</KnobDial>
	<KnobValue />
	<KnobLabel />
</Knob>
```

`KnobTrack`, `KnobRange` and `KnobPointer` are SVG elements inside a 100 × 100 view box, so you can add your own shapes next to them. `KnobScale` and `KnobCap` are another set of parts for the same dial, shown in the [volume dial](#volume-dial) and the [metal knobs](#metal-knobs).

## Examples

### Drag directions

Choose `dragDirection` to match how you want to move the dial:

- `vertical`, the default: drag up to increase the value, down to decrease it. The cursor shows an up/down arrow while dragging.
- `horizontal`: drag right to increase the value, left to decrease it. The cursor shows a left/right arrow while dragging.
- `circular`: turn clockwise to increase the value, anticlockwise to decrease it.

Hold Shift for finer control in all three modes. For vertical and horizontal drags, `sensitivity` sets the distance in pixels for the full range. The default is `200`.

<ComponentPreview name="knob-drag-directions" />

### Sizes and disabled

<ComponentPreview name="knob-sizes" />

### Volume dial

A hi-fi volume dial: `KnobScale` draws numbered ticks around the dial and `KnobCap` a brushed aluminium cap with an indicator dot. Give it room for the numbers with a larger `--knob-size`. `clickSound` adds a soft tick on each long graduation.

<ComponentPreview name="knob-volume" />

Ticks between `origin` and the value light up, so a bipolar knob lights from its centre. The ticks are spread evenly along the arc, so they follow `scale="log"` too.

### Metal knobs

The same aluminium at everyday sizes: swap `KnobPointer` for `<KnobCap variant="mini" />`. The mini cap fills the inside of `KnobTrack` and marks the value with an engraved line, which still reads at `size="sm"`.

<ComponentPreview name="knob-metal" />

```svelte
<KnobDial>
	<KnobTrack />
	<KnobRange />
	<KnobCap variant="mini" />
</KnobDial>
```

## Theming

| Variable or attribute | Meaning                                   |
| --------------------- | ----------------------------------------- |
| `--knob-size`         | Set by `size`                             |
| `--knob-angle`        | The pointer angle, live, for custom skins |
| `data-dragging`       | On the dial while dragging                |
| `data-at-origin`      | The value sits at `origin`                |
| `--knob-cap-metal`    | `KnobCap`'s metal, white by default       |
| `--knob-cap-shade`    | `KnobCap`'s shading, black by default     |
| `--knob-cap-pitch`    | Spacing of `KnobCap`'s brushed rings      |
| `data-active`         | On `KnobScale` ticks inside the lit range |
| `data-major`          | On `KnobScale`'s long ticks               |

Style the SVG parts with `stroke-*` and `fill-*` classes. Restyle the lit ticks from the scale, for example `<KnobScale class="**:data-active:stroke-primary" />`.

## Accessibility

The dial is a focusable slider with `aria-valuetext` from `format`, named by `KnobLabel`. It supports the same keys as the [fader](/docs/components/fader#keyboard), and Enter opens the value editor.

## API reference

<PropsTable
	rows={[
		["value", "number", "resetValue ?? min", "Bindable."],
		[
			"onValueChange",
			"(value, details) => void",
			null,
			"details.reason: drag, keyboard, wheel, reset or input.",
		],
		["onValueCommit", "(value) => void", null, null],
		["min / max", "number", "0 / 100", null],
		[
			"step / largeStep / fineStep",
			"number",
			"1 / 10 / step ÷ 10",
			"fineStep applies to Shift+drag, Shift+wheel and Alt+arrow.",
		],
		[
			"resetValue",
			"number",
			"the first value, or min",
			"Double-click or Alt+click restores it.",
		],
		["origin", "number", "min", "Where the arc starts; the centre for bipolar knobs."],
		["arc", "number", "270", "Sweep in degrees."],
		[
			"dragDirection",
			'"vertical" | "horizontal" | "circular"',
			'"vertical"',
			"Circular turns the knob as you circle around it. Vertical and horizontal drag in a straight line, as in most DAWs.",
		],
		["sensitivity", "number", "200", "Pixels of vertical or horizontal drag for the full range."],
		["scale", '"linear" | "log"', '"linear"', null],
		["allowWheel", "boolean", "false", null],
		[
			"clickSound",
			"boolean",
			"false",
			"Plays a soft tick on each graduation: KnobScale's long ticks, or every largeStep without a scale. At most every 30 ms, through the shared AudioContext.",
		],
		["format", "(value: number) => string", "String", null],
		[
			"parse",
			"(text: string) => number | null",
			"parseKnobValue",
			'Reads a typed value. The default takes the first number and reads "k" as thousands. Use parsePan for pan knobs.',
		],
		["size", '"sm" | "default" | "lg"', '"default"', null],
		["disabled", "boolean", "false", null],
	]}
/>

### KnobValue

<PropsTable
	rows={[["editable", "boolean", "true", "Double-click, or Enter on the dial, to type a value."]]}
/>

### KnobScale

<PropsTable
	rows={[
		["ticks", "number", "50", "Divisions across the arc; draws ticks + 1 marks."],
		["majorEvery", "number", "5", "Every nth tick is long."],
		["labelEvery", "number", "10", "Every nth tick is numbered. 0 hides the numbers."],
		["format", "(value: number) => string", "the knob's format", null],
	]}
/>

### KnobCap

<PropsTable
	rows={[
		[
			"variant",
			'"default" | "mini"',
			'"default"',
			"default sits inside KnobScale with an indicator dot. mini fills the inside of KnobTrack with an engraved line, for small knobs.",
		],
	]}
/>
