A highly customizable and accessible knob component for Svelte applications.
Ported from rc-knob originally created by eskimoblood and updated by vallsv.
The component architecture separates user interaction and value calculation from the visual rendering. This separation allows for maximum flexibility and customization:
<Knob>
: The root component that handles all user interactions and logic<Arc>
: Renders the knob's arc<Pointer>
: Displays the knob's pointer<Scale>
: Shows the knob's scale<Value>
: Renders the current valueThe component uses Svelte 5's runes and a dedicated state store created via createKnobState
. The <Knob>
component provides state and configuration to child components through Svelte's context system with the following structure:
interface KnobContext {
state: {
isActive: boolean;
min: number;
max: number;
value: number | null;
percentage: number | null;
mouseAngle: number | null;
startPercentage: number | null;
startValue: number | null;
multiRotation: boolean;
angleOffset: number;
angleRange: number;
tracking: boolean;
size: number;
steps?: number;
};
config: {
size: number;
angleOffset: number;
angleRange: number;
steps?: number;
};
}
Instead of passing callback functions directly to child components, we use Svelte 5's snippet feature. Each visual component accepts a snippet that receives the necessary props for custom rendering. All components render SVG elements and must be placed within the <Knob>
's SVG container.
<script>
import { Knob, Pointer, Value } from 'svelte-knob';
let value = $state(0);
</script>
<Knob bind:value min={0} max={100} size={200}>
<Value />
<Pointer width={1} height={2}>
{#snippet pointer(props)}
<!-- Custom pointer implementation -->
<rect {...props} />
{/snippet}
</Pointer>
</Knob>
<Knob>
The root component that handles all user interactions. It renders a <div>
with ARIA slider attributes containing an SVG element where all child components are rendered. The knob supports:
tab
value
propinitialValue
propProp | Type | Default | Required | Description |
---|---|---|---|---|
min |
number | Yes | Minimum value | |
max |
number | Yes | Maximum value | |
size |
number | Yes | Width and height in pixels | |
angleOffset |
number | 0 | No | Starting angle offset in degrees (0° is at top, clockwise) |
angleRange |
number | 360 | No | Total rotation range in degrees (clockwise) |
ariaLabelledBy |
string | No | Sets the aria-labelledby attribute |
|
ariaValueText |
string | No | Sets the aria-valuetext attribute |
|
class |
string | No | CSS class for the container div | |
svgClass |
string | No | CSS class for the SVG element | |
initialValue |
number | null | No | Starting value for uncontrolled mode |
value |
number | null | No | Current value for controlled mode |
interactiveHook |
function | No | Customizes knob behavior during mouse interaction (see details below) | |
multiRotation |
boolean | false | No | Enables unlimited rotation (ignores min/max limits) |
onChange |
function | noop | No | Callback for value changes (after interaction ends) |
onEnd |
function | noop | No | Callback when dragging ends |
onInteractiveChange |
function | noop | No | Callback during dragging |
onStart |
function | noop | No | Callback when dragging starts |
readOnly |
boolean | false | No | Disables user interaction |
snap |
boolean | false | No | Enables snapping to steps (requires steps to be set) |
steps |
number | No | Number of snap points | |
tracking |
boolean | true | No | Controls when onChange fires during dragging |
useMouseWheel |
boolean | true | No | Enables mouse wheel interaction |
The interactiveHook
function allows fine-grained control over knob behavior based on mouse position. It receives an event object with:
interface InteractiveHookEvent {
mouseRadius: number; // Distance from knob center
mouseAngle: number; // Angle in degrees (0-360, 0° at top, clockwise)
mouseX: number; // X position relative to center
mouseY: number; // Y position relative to center
ctrlKey: boolean; // Ctrl key state
altKey: boolean; // Alt key state
metaKey: boolean; // Meta key state
shiftKey: boolean; // Shift key state
}
The function should return an object that can include:
interface InteractiveHookResult {
readOnly?: boolean; // Disables interaction when true
steps?: number; // Number of snap points
}
Example:
function interactiveHook(e: InteractiveHookEvent): InteractiveHookResult {
if (e.mouseRadius < 20) {
return { readOnly: true }; // Disable interaction near center
}
return {};
}
<Arc>
Renders an arc showing the current value of the knob. Uses the <Range>
component internally to render both the value arc and an optional background arc.
Prop | Type | Default | Required | Description |
---|---|---|---|---|
arcWidth |
number | Yes | Width of the arc in pixels | |
percentage |
number | context value | No | Current percentage (0-1) to display |
color |
string | 'currentColor' | No | Color of the value arc |
background |
string | undefined | No | Color of the background arc. If not set, no background arc is rendered |
radius |
number | context size/2 | No | Outer radius of the arc in pixels |
class |
string | undefined | No | CSS class for the value arc |
activeClass |
string | undefined | No | CSS class for the background arc |
<Range>
Low-level component that renders an SVG path representing a range between two percentages. Used internally by the <Arc>
component but can also be used directly for custom range visualizations.
The path is generated using SVG arc commands and handles special cases for full circles and tiny angles. It automatically clamps angles to prevent rendering issues at exact 360/-360 degrees.
Prop | Type | Default | Description |
---|---|---|---|
arcWidth |
number | Required | Width of the arc in pixels |
color |
string | 'currentColor' | Color of the range arc |
percentage |
number | context value | Current percentage value |
percentageFrom |
number | null | Starting percentage of range |
percentageTo |
number | null | Ending percentage of range |
radius |
number | knob size / 2 | Outer radius of the arc |
class |
string | '' | CSS class for the range |
The component uses the following logic to determine the actual range to render:
percentageFrom
and percentageTo
are provided, uses them directlypercentageFrom
is provided, uses it as start and percentage
as endpercentageTo
is provided, uses percentage
as start and percentageTo
as endpercentage
as end<Spiral>
Renders a spiral path between two points with varying radius. Particularly useful for multi-rotation knobs to show winding progression. The spiral is rendered as an SVG path with smooth transitions between points.
Prop | Type | Default | Description |
---|---|---|---|
arcWidth |
number | Required | Width of the spiral line |
color |
string | 'currentColor' | Color of the spiral |
percentage |
number | context value | Current percentage value |
percentageFrom |
number | null | Starting percentage (defaults to 0 or percentage) |
percentageTo |
number | null | Ending percentage (defaults to percentage) |
radiusFrom |
number | null | Starting radius |
radiusTo |
number | null | Ending radius |
The component uses the following logic to determine the actual range:
percentageFrom
defaults to 0 if percentageTo
is set, otherwise uses percentage
percentageTo
defaults to percentage
radiusFrom
and radiusTo
must be provided for the spiral to render<Pointer>
Visual indicator component that shows the current value of the knob. Supports different shapes and custom SVG elements.
Prop | Type | Default | Required | Description |
---|---|---|---|---|
width |
number | Yes | Width of the pointer | |
height |
number | width | No | Height of the pointer |
useRotation |
boolean | true | No | Use rotation transform instead of position transform |
type |
string | No | Shape type: 'rect', 'circle', or 'triangle' | |
color |
string | 'currentColor' | No | Fill color of the pointer |
radius |
number | size / 2 | No | Distance from center to pointer |
center |
number | size / 2 | No | Center point of rotation |
class |
string | '' | No | Additional CSS class |
percentage |
number | context value | No | Percentage value to display |
rect
: Rectangular pointer centered on rotation pointcircle
: Circular pointer with radius equal to widthtriangle
: Triangular pointer pointing outward{ width, height, percentage }
The pointer can be positioned in two ways, controlled by the useRotation
prop:
true
: Rotates pointer around center point using a rotation transformfalse
: Positions pointer at calculated x,y coordinates using a translation transformExample with custom pointer:
<Pointer width={10} height={20}>
{#snippet pointer({ width, height, percentage })}
<!-- Custom pointer implementation -->
<rect x={-width * 0.5} {width} {height} fill="currentColor" />
{/snippet}
</Pointer>
<Label>
Displays text labels around the knob at specified angles. Labels are positioned in polar coordinates and automatically rotated to maintain readability.
Prop | Type | Default | Required | Description |
---|---|---|---|---|
label |
string | Yes | Text to display | |
radius |
number | Yes | Distance from center | |
percentage |
number | Yes | Position around the circle (0-1) | |
center |
number | context size/2 | No | Center point |
color |
string | 'currentColor' | No | Text color |
class |
string | '' | No | CSS class for the text element |
userSelect |
string | 'none' | No | CSS user-select property value |
<Scale>
Renders a radial scale with configurable tick marks around the knob. Supports circle and rect tick shapes, or custom rendered ticks.
Prop | Type | Default | Required | Description |
---|---|---|---|---|
tickWidth |
number | Yes | Width of each tick | |
tickHeight |
number | Yes | Height of each tick (for rect type) | |
type |
string | 'rect' | No | Shape type: 'rect' or 'circle' |
radius |
number | context size/2 | No | Distance from center to ticks |
color |
string | 'currentColor' | No | Default tick color |
activeColor |
string | color | No | Color for active tick |
class |
string | '' | No | Default tick class |
activeClass |
string | class | No | Class for active tick |
steps |
number | context steps → prop steps → 10 | No | Number of ticks |
The scale can be customized using a snippet that receives:
interface RenderCustomProps {
tickWidth: number; // Width of tick
tickHeight: number; // Height of tick
translateX: number; // X position
translateY: number; // Y position
angleOffset: number; // Starting angle
stepSize: number; // Angle between ticks
center: number; // Center point
color: string; // Default color
className?: string; // Default class
active: number; // Index of active tick
activeColor: string; // Active tick color
activeClassName: string; // Active tick class
i: number; // Current tick index
steps: number; // Total number of steps
percentage: number; // Current percentage
}
Example with custom ticks:
<Scale tickWidth={2} tickHeight={10}>
{#snippet custom(props)}
<!-- Custom tick implementation -->
<rect
class={[props.i === props.active ? props.activeClassName : props.className]}
fill={props.i === props.active ? props.activeColor : props.color}
stroke="none"
width={props.tickWidth}
height={props.tickHeight}
transform={`
rotate(${props.angleOffset + props.stepSize * props.i} ${props.center} ${props.center})
translate(${props.translateX} ${props.translateY})
`}
/>
{/snippet}
</Scale>
<Value>
Displays the current value of the knob as text. Supports decimal places formatting and vertical positioning.
Prop | Type | Default | Required | Description |
---|---|---|---|---|
value |
number | context value | No | Value to display |
radius |
number | context size/2 | No | Distance from center |
decimalPlace |
number | 0 | No | Number of decimal places |
marginBottom |
number | 0 | No | Bottom margin in pixels |
color |
string | 'currentColor' | No | Text color |
class |
string | '' | No | CSS class for the text element |
The component automatically handles negative zero values by removing the negative sign.
Note: All components require being children of a <Knob>
component as they rely on its context for default values and state management.