Dual Band Selector
dual-band-selectorControlsTwo caps on one frequency track with the band between them lit — the control a tone section wants instead of two knobs, because what matters is where the band sits and how wide it is, and a bar shows that where two knobs make you compute it. Press the band to move both caps at their current width, press the open track to jump the nearer one, Shift for a fine pass. The caps stop against each other at a minimum ratio (log taper) or difference (linear) rather than crossing, so the band can never invert. Each cap is its own slider in the tab order, and reports the limit the other cap imposes as its own aria range.
Install
Copies the source into your project. Anything it depends on comes with it.
$ npx shadcn add @syntho-ui/dual-band-selectorPulls in
First time? Add the registry to your components.json once:
{
"registries": {
"@syntho-ui": "https://engine.syntho.app/r/{name}.json"
}
}Usage
Controlled or uncontrolled, whichever suits the surrounding state.
import { DualBandSelector } from "@/components/ui/dual-band-selector"
<DualBandSelector
low={tone.lowCut}
high={tone.highCut}
min={20}
max={20000}
defaultLow={20}
defaultHigh={20000}
onLowChange={(hz) => patch.set({ lowCut: hz })}
onHighChange={(hz) => patch.set({ highCut: hz })}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| low / high | number | — | The two cap values, in engine units. Controlled only: they are engine parameters, and a control that also remembered them would be a second source of truth. A crossed or out-of-range pair is corrected before it is drawn. |
| min / max | number | — | The ends of the track — 20 and 20000 for an audio-band bar. |
| taper | "log" | "linear" | "log" | How values map onto the track. The log taper is the same mapping every knob in the kit uses, so an octave is an octave anywhere along it. |
| minGap | number | 1.25 (log) / 0 (linear) | How close the caps may come: a high/low ratio on a log track, a difference on a linear one. The moved cap stops there instead of pushing its neighbour along. |
| onLowChange / onHighChange | (value: number) => void | — | One call per cap that actually moved. During a band drag both fire, the leading cap first, so a host that clamps each against the other still accepts both. |
| onChangeStart / onChangeEnd | (handle: "low" | "high") => void | — | Gesture bracket for undo coalescing or a host's beginEdit/endEdit. A band drag brackets both caps; a wheel notch or a key press is its own bracket. |
| formatValue | (value: number) => string | 220 Hz / 6.0 kHz / 20 kHz | Readouts and aria-valuetext, so the two never disagree. |
| lowLabel / highLabel | string | "Low Cut" / "High Cut" | Caption above each cap, and each cap's accessible name. |
| defaultLow / defaultHigh | number | — | Double-click reset target for that cap. Omitted, double-click does nothing. |
| accent | string | — | Colour of the lit band and the caps. Falls back to the control-theme accent, like every other control. |
| size | "sm" | "md" | "lg" | "md" | Groove height, cap size and readout scale. |
| width | number | string | "100%" | Track length. Fills its container by default. |
| onInteraction | (event) => void | — | Fires once, on the first gesture, synchronously inside the event — for resuming a suspended AudioContext. |
Interaction
- Drag a cap Moves that cap only; it stops against the other at minGap.
- Drag the band Moves both caps, keeping the band's width, and parks at the rail.
- Press the open track Jumps the nearer cap there and carries on into a drag.
- Shift Fine pass, relative and anchored where Shift went down.
- Wheel Steps the cap nearest the pointer; Shift steps finely.
- Arrows / Page / Home / End Step the focused cap by a fraction of the track, ten of those, or run it to its limit.
- Double-click a cap Resets it to defaultLow / defaultHigh.