Knob
Interactive rotary knob component implemented in SVG.
Source Style- React
- Svelte
- Vue
function App() { const [value, setValue] = useState(64) return ( <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column', }} > <Knob.Root className={knobTheme.root} aria-label="Level" value={value} min={0} max={100} size={50} onChange={(v) => setValue(v)} > <Knob.SVGRoot> <Knob.ActiveLine className={knobTheme.activeLine} /> <Knob.InactiveLine className={knobTheme.inactiveLine} /> <Knob.Thumb className={knobTheme.thumb} classes={{ line: knobTheme.thumbLine }} /> </Knob.SVGRoot> </Knob.Root> {value} </div> ) }
/** The demo theme for `Knob`, as a CSS Module: one class per part, applied by* the caller. Copy this file into your project and edit it.** The knob draws itself as an SVG, so this is colour and cursor only.*/.root {/* The knob has no intrinsic size: the SVG inside scales to its box.Override with the `size` prop, or by setting this variable. */--knob-size: 50px;display: inline-block;width: var(--knob-size);height: var(--knob-size);/* A knob is a fixed-size control, like a button. Without this a flex rowthat runs short shrinks the width and leaves the height alone, so the boxstops being square and the knob sits in it with dead space below.`aspect-ratio` is not the answer: it is ignored while both width andheight are definite, and dropping the height to free it up lets`align-items: stretch` pull the box the other way. */flex-shrink: 0;cursor: grab;outline: 0;&[data-readonly] {cursor: not-allowed;}&[data-dragging] {cursor: inherit;}}.activeLine {color: oklch(69.2% 0.17548 266.373);:where(.root:focus, .root:hover) & {color: oklch(59.5% 0.17548 266.373);}:where(.root[data-disabled]) & {color: oklch(50.5% 0.03307 270.021);}}.inactiveLine {color: oklch(95% 0 0);:where(.root:focus, .root:hover) & {color: oklch(90% 0 0);}}.thumb {color: oklch(85.7% 0 0);:where(.root:focus, .root:hover) & {color: oklch(84.5% 0 0);}}.thumbLine {color: oklch(94.2% 0 0);}/** Dark mode follows whatever marks the page — `.dark` and `[data-theme=dark]`* are what this site and Storybook set.*/:global(.dark) .activeLine,:global([data-theme='dark']) .activeLine {color: oklch(59.5% 0.17548 266.373);:where(.root:focus, .root:hover) & {color: oklch(65.2% 0.17548 266.373);}}:global(.dark) .inactiveLine,:global([data-theme='dark']) .inactiveLine {color: oklch(41.7% 0 0);:where(.root:focus, .root:hover) & {color: oklch(44.59% 0 0);}}:global(.dark) .thumb,:global([data-theme='dark']) .thumb {color: oklch(48.6% 0 0);:where(.root:focus, .root:hover) & {color: oklch(51.7% 0 0);}}:global(.dark) .thumbLine,:global([data-theme='dark']) .thumbLine {color: oklch(84.2% 0 0);}
<script lang="ts">
import { Knob } from '@tremolo-ui/svelte'
// Copy this file from the Styling page into your own project.
import knobTheme from './Knob.module.css'
let value = $state(64)
</script>
<Knob.Root
class={knobTheme.root}
aria-label="Level"
bind:value
min={0}
max={100}
size={50}
>
<Knob.SVGRoot>
<Knob.ActiveLine class={knobTheme.activeLine} />
<Knob.InactiveLine class={knobTheme.inactiveLine} />
<Knob.Thumb
class={knobTheme.thumb}
classes={{ line: knobTheme.thumbLine }}
/>
</Knob.SVGRoot>
</Knob.Root>
{value}
<script setup lang="ts">
import { ref } from 'vue'
import {
Knob,
KnobActiveLine,
KnobInactiveLine,
KnobSVGRoot,
KnobThumb,
} from '@tremolo-ui/vue'
// Copy this file from the Styling page into your own project.
import knobTheme from './Knob.module.css'
const value = ref(64)
</script>
<template>
<Knob
v-model="value"
:class="knobTheme.root"
aria-label="Level"
:min="0"
:max="100"
:size="50"
>
<KnobSVGRoot>
<KnobActiveLine :class="knobTheme.activeLine" />
<KnobInactiveLine :class="knobTheme.inactiveLine" />
<KnobThumb
:class="knobTheme.thumb"
:classes="{ line: knobTheme.thumbLine }"
/>
</KnobSVGRoot>
</Knob>
{{ value }}
</template>
Import
- React
- Svelte
- Vue
import { Knob } from '@tremolo-ui/react'
import { Knob } from '@tremolo-ui/svelte'
import {
Knob,
KnobActiveLine,
KnobInactiveLine,
KnobSVGRoot,
KnobThumb,
} from '@tremolo-ui/vue'
API Reference
Each part lists the props of its own. It also takes the attributes of the
element it renders, such as className and style.
In Svelte and Vue the props are the same, written their way — class rather
than className, bind:value or v-model, snippets or slots for content. See
Svelte and Vue.
State is on the element as a data attribute, so it is a selector away — see Styling. An attribute is only there while its state is on.
Root
Holds the value and handles the drag, the wheel and the arrow keys. It is the control itself: it takes the focus and carries the ARIA.
| Prop | Type | Default |
|---|---|---|
value* | number | — |
min* | number | — |
max* | number | — |
step | number | 1 |
scale | Scale | linearScale |
resetValue | number | null | startValue |
startValue | number | min |
size | string | number | — |
dragCursor | Cursor | 'grabbing' |
wheel | ModifierValue<InputEventOption> | null | ['raw', 1] |
dragSensitivity | ModifierValue<number> | { default: 1, shift: 0.1 } |
pointerLock | boolean | false |
keyboard | ModifierValue<InputEventOption> | null | { default: ['raw', 1], shift: ['raw', 0.1] } |
disabled | boolean | — |
readOnly | boolean | — |
angleRange | number | 270 |
onChange | (value: number) => void | — |
onChangeStart | (value: number, source: ChangeSource) => void | — |
onChangeEnd | (value: number, source: ChangeSource) => void | — |
changeEndDelay | number | 500 |
children* | ReactNode | — |
actionsRef | Ref<KnobMethods> | — |
Full reference: KnobProps
| Custom property | Written by | What it sets |
|---|---|---|
--knob-size | size | The width and height of the knob. |
| Data attribute | Values |
|---|---|
[data-disabled] | Present when disabled is set |
[data-readonly] | Present when readonly is set |
[data-dragging] | Present while a drag is in progress |
SVGRoot
The <svg> the drawing parts go in. It sets the view box they are drawn
against.
| Prop | Type | Default |
|---|---|---|
children* | ReactNode | — |
Full reference: KnobSVGRootProps
InactiveLine
The arc outside the value. It has no props of its own and takes the attributes
of an SVG <path>. The arc is kept inside the view box by the strokeWidth
you give it.
ActiveLine
The arc between startValue and the value. Like Knob.InactiveLine, it takes
the attributes of an SVG <path>.
Thumb
The circle that turns with the value, and the line on it that points at the value.
| Prop | Type | Default |
|---|---|---|
color | string | 'currentColor' |
lineColor | string | 'currentColor' |
size | number | 84 |
lineWeight | number | 6 |
lineLength | number | 35 |
classes | { line?: string } | — |
Full reference: KnobThumbProps