EQ Editor

eq-editorControls

The interactive layer over eq-display, which stays a pure renderer. Drag a handle to move a band in frequency and gain, wheel over it for Q, tab to it and use the arrows for all three — and because what a band will let you change is a property of the band, an editor over four peaking bands and one over a fixed low/mid/high stack are the same component with different inputs. Two drawings of the identical model: parametric puts handles on the curve, bars stands each band as a column at its own frequency with the curve still behind it, which makes the set of gains legible where the curve makes the result legible. Controlled — bands in, changes out, no internal copies — with the band model, hit testing and step maths in a pure core beside it and pinned in Node.

Low 90 Hz +4.0 dB · Q 0.70Low mid 420 Hz -3.5 dB · Q 1.20Presence 2.60 kHz +3.0 dB · Q 1.60Air 9.00 kHz +5.0 dB · Q 0.70

Drag a handle for frequency and gain, wheel over one for Q, or tab to it and use the arrows — Alt+Left/Right (or [ and ]) for Q, Shift for a fine pass. In bars mode the columns stand at their own frequencies, so the curve behind them is the curve they make.

Install

Copies the source into your project. Anything it depends on comes with it.

$ npx shadcn add @syntho-ui/eq-editor

Pulls in

eq-displayknob-coreresponse-geometryuse-surface-pointeruse-wheelcontrol-theme

First time? Add the registry to your components.json once:

components.json
{
  "registries": {
    "@syntho-ui": "https://engine.syntho.app/r/{name}.json"
  }
}

Usage

Controlled or uncontrolled, whichever suits the surrounding state.

example.tsx
import { EQEditor } from "@/components/ui/eq-editor"

const [bands, setBands] = useState([
  { type: "lowshelf", freq: 90, gain: 4, q: 0.7, label: "Low" },
  { type: "peaking", freq: 1200, gain: -3, q: 1.4, label: "Mid" },
  // A derived centre frequency locks its axis: the handle moves in gain only.
  { type: "highshelf", freq: 8000, gain: 5, q: 0.7, label: "Air", freqRange: null },
])

<EQEditor mode="parametric" bands={bands} onBandsChange={setBands} />

Props

PropTypeDefaultDescription
bandsEQEditorBand[]Everything EQBand carries, plus a label and freqRange / gainRange / qRange. Each range is three-valued: omitted takes the editor's default, an object pins that axis's bounds (and an optional step), and null locks the axis — the handle will not move on it and its keys fall through so the page still scrolls.
onBandsChange(bands: EQEditorBand[]) => voidThe whole next array, for a consumer that holds one.
onBandChange(index, patch, band) => voidOne band's change, for a consumer whose bands are separate parameters. Fires alongside onBandsChange, so neither consumer has to reconstruct what the other wanted.
onChangeEnd() => voidFires once when a drag ends — the moment to commit an undo entry.
mode"parametric" | "bars""parametric"Which drawing the same bands get. Switching modes changes nothing about the bands; in bars mode a column keeps its place on the frequency axis, so a drag is vertical only.
defaults{ freq?, gain?, q? }Ranges for any axis a band does not pin itself. Defaults to 20–20 kHz, ±18 dB and Q 0.1–18.
minFrequency / maxFrequencynumber20 / 20000The frequency axis bounds, drawn logarithmically. Forwarded to the display.
decibelRangenumber18Vertical range, in dB either side of flat.
showBands / showGrid / fillbooleanForwarded to the underlying EQDisplay. showBands defaults on in parametric mode.
showLabelsbooleantrueDraw each band's label under its handle or column.
showGuidesbooleanVertical guide through each band's frequency. On in parametric mode, off in bars mode where the column is the guide.
cursorEQCursor | nullA modulation cursor riding the curve — forwarded to the display.
colorstringOverride the accent for this instance, curve included.
heightnumber200Plot height in pixels; width defaults to 100%.

Interaction

  • Drag with pointer capture, so the gesture keeps tracking past the edge of the control.
  • Shift while dragging for a fine pass; release it mid-gesture and coarse tracking resumes.
  • Double-click resets to defaultValue.
  • Arrow keys step normally, Shift+arrow steps finely, PageUp/PageDown jump by ten, Home/End go to the extremes.
  • Proper slider and switch roles with live aria-valuetext, so a screen reader announces the formatted value rather than the raw number.
  • The scroll wheel adjusts the value while the pointer is over the control; stepped ranges move at least one declared step per notch by default.