Lab

Custom elements

Every primitive as framework-agnostic Web Components, with their parts, state attributes and events, in Vue, Svelte, plain HTML and any framework.

@audio-ui/elements provides every primitive as a set of unstyled, accessible custom elements, built on @audio-ui/core: their parts, keyboard interactions, ARIA semantics and data-* state attributes. The elements work in plain HTML and in any framework that renders HTML, such as Vue, Svelte, Angular or Lit. Every element has a page with its attributes, its parts, a live preview and its usage in your framework.

Installation

pnpm add @audio-ui/elements

Each primitive is a separate entry point, @audio-ui/elements/<primitive>, which registers its elements when imported. The package root registers every primitive. Import an entry once, before the elements are used:

import "@audio-ui/elements/knob";

Registration is a no-op on the server: server-rendered markup upgrades once the module loads in the browser.

Usage

<audio-ui-knob min="20" max="20000" default-value="1000" scale="log">
  <audio-ui-knob-label>Cutoff</audio-ui-knob-label>
  <audio-ui-knob-control>
    <audio-ui-knob-track></audio-ui-knob-track>
    <audio-ui-knob-indicator></audio-ui-knob-indicator>
    <audio-ui-knob-thumb></audio-ui-knob-thumb>
  </audio-ui-knob-control>
  <audio-ui-knob-value></audio-ui-knob-value>
</audio-ui-knob>

<script type="module">
  import "@audio-ui/elements/knob";

  const knob = document.querySelector("audio-ui-knob");
  knob.addEventListener("input", (event) => setCutoff(event.detail.value));
</script>

Value and events

Every control follows the same contract:

MemberKindDescription
inputEventFires on every change. event.detail holds the new value and the reason for the change, such as "drag", "keyboard", "wheel", "reset" or "input-change". Calling preventDefault() rejects the change.
changeEventFires once when an interaction that changed the value ends. Use it to commit: start a note, record an undo step, save.
valuePropertyReads the current value, and sets it. While input is dispatched it reads the value proposed, as a native input's does, so event.target.value is the new one. Framework bindings (v-model in Vue, value={} in Svelte) read and write this property. During a drag, the control follows the pointer even when the bound value lags behind.
nameAttributeSubmits the value with the enclosing form, through the control's native range input.

A part that holds a value of its own fires events prefixed with its name, for example clipinput and clipchange on a timeline clip. These never collide with the control's own input and change, and they bubble, so a single listener on the root can handle every part.

Styling

The elements apply only the styles their behavior depends on: the display of each box, the positioning of placed parts, touch-action on interactive surfaces. These rules are wrapped in :where(), so they carry no specificity and any rule of yours takes precedence. No color, size or spacing is set. Style the parts by tag and by the same state attributes as the React parts, with a stylesheet or with classes. Parts drawn as SVG use currentColor.

The examples on every element's page do both, and the Styling choice of the customizer says which one they show. They read the theme's variables. The CSS examples carry the default theme's values as fallbacks, so they work as pasted; to change the look, define the variables once in your own stylesheet. The Tailwind examples need them, named for their classes:

:root {
  --background: oklch(1 0 0);
  --border: oklch(0.922 0 0);
  --card: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --input: oklch(0.922 0 0);
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --popover: oklch(1 0 0);
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);
  --radius: 0.625rem;
  --ring: oklch(0.708 0 0);
}

.dark {
  --background: oklch(0.145 0 0);
  --border: oklch(1 0 0 / 10%);
  --card: oklch(0.205 0 0);
  --foreground: oklch(0.985 0 0);
  --input: oklch(1 0 0 / 15%);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --popover: oklch(0.205 0 0);
  --primary: oklch(0.922 0 0);
  --primary-foreground: oklch(0.205 0 0);
  --ring: oklch(0.556 0 0);
}

The Accent, Radius and Font of the customizer restyle every preview, and Copy CSS gives this stylesheet with your choices.

Elements

Each primitive is a set of elements, with a page of its attributes, its parts and a live preview:

ElementWhat it is
Knob<audio-ui-knob> is the root of a rotary control. It owns the value and renders its parts from it.
Fader<audio-ui-fader> is the root of a linear control, with audio defaults: vertical, from -60 to +6. It has role="group", labelled by its label part.
Pad<audio-ui-pad> is a grid of velocity-sensitive pads with role="group". A pad sounds while held, by pointer or with Enter and Space, and each pointer holds its own pad. Velocity comes from pen or touch pressure, or from the vertical press position. The pads share one tab stop and are navigated with the arrow keys.
Piano<audio-ui-piano> is a playable keyboard with role="group". A key sounds while pressed, by pointer or with Enter and Space. Dragging glides across keys, and each pointer holds its own key, so chords can be played on touch screens. The keys share one tab stop and are navigated with the arrow keys, by octave with Shift.
XY pad<audio-ui-xypad> controls a two-dimensional value { x, y }. A press moves the point to the pointer and a drag follows it; the wheel, the trackpad and the arrow keys move it; a double click or Delete resets it. The position is exposed as --xy-pad-x (from the left) and --xy-pad-y (from the top), as percentages.
Meter<audio-ui-meter> displays one or more level channels. Levels come from the value and peak attributes or properties, one per channel, or from a live source property, { getSnapshot, subscribe }, whose frames are written directly to the parts. A click clears clip indicators latched by a live source.
Sequencer<audio-ui-sequencer> is a step grid with one row per sound and role="group". Pressing a cell toggles it, and dragging applies the new state to every cell crossed. The cells share one tab stop and are navigated with the arrow keys. Rows and steps are generated from templates:
Envelope<audio-ui-envelope> is a breakpoint envelope editor with role="group". Points are dragged to move, added with a double click on the graph, and removed with a double click, Alt + click or Delete. Each segment has a curve handle at its midpoint, and Alt + drag on the graph bends the segment under the pointer. The points share one tab stop: Page Up and Page Down (or Home and End) move between them, and the arrow keys move the focused point. Points and segment handles are generated, from a <template> when one is provided:
Plot<audio-ui-plot> defines two axes and lays out everything drawn on them in axis units, for response curves, filter displays and transfer functions. Each drawing part renders its own SVG covering the plot, in currentColor, with a constant stroke width. Curves take their function and traces their live source as properties:
Waveform<audio-ui-waveform> displays audio and, with a control part, seeks through it: a press seeks to the pointer, a drag scrubs, and the keyboard steps. The shape comes from the peaks property or a live levels source; the position from value or a live source, exposed as --waveform-position:
Timeline<audio-ui-timeline> is the root of an arrangement: a ruler that sets the position, lanes of clips that move, trim and fade, loop ranges, and a viewport that scrolls and zooms. Units are arbitrary: seconds, beats or steps.
Piano roll<audio-ui-piano-roll> is a note editor placed inside an <audio-ui-timeline>, which provides its length, zoom, grid and position. Its entry point also registers the timeline and piano elements. Notes are set through the value property as { id, pitch, start, length, velocity?, chance?, muted? }[], and the selection through the selection property. Rows, notes and velocity bars are generated from them:

Data attributes

Parts reflect the state of their control:

Data attributes · 9

data-disabled
Present when disabled.
data-dragging
Present while a pointer drags the control.
data-drag-mode
On knob parts, the drag-mode, for cursor styling.
data-orientation
On fader parts, horizontal or vertical.
data-active
On a pad or a piano key while it sounds.
data-on
On a sequencer cell or velocity bar whose step is on.
data-current
On the cells of the current step.
data-painting
On the sequencer and its parts during a paint gesture.
data-blocked
On an envelope graph or point, briefly, when an add or remove is refused.

The package

pnpm add @audio-ui/elements
import "@audio-ui/elements";
ExportKindWhat it is
ENVELOPE_TAGSconstThe names the envelope's elements are registered under, frozen: a page cannot rename them by accident.
EnvelopeChangeEventtypeFired once an interaction that changed the points ends.
EnvelopeElementclassThe root of a breakpoint envelope editor, with role="group".
EnvelopeElementEventMapinterfaceThe events an envelope fires, as addEventListener types them.
EnvelopeEventDetailinterfaceWhat an envelope's input and change events carry, besides the envelope itself as their target.
EnvelopeFillElementclassThe area under the curve, in currentColor.
EnvelopeGraphElementclassThe drawing surface, filling the envelope (position: absolute; inset: 0) and containing the line and the fill.
EnvelopeInputEventtypeFired on every change.
EnvelopeLineElementclassThe curve through the points, in currentColor, with a stroke width that stays constant as the graph scales.
EnvelopePointElementclassA draggable handle for the point at index, positioned with left and bottom.
EnvelopePointsElementclassGenerates an <audio-ui-envelope-point> per point, in x order, from its <template> when present.
EnvelopeSegmentElementclassA curve handle at the midpoint of the segment that starts at point index, a role="slider" from -1 to 1.
EnvelopeSegmentsElementclassGenerates an <audio-ui-envelope-segment> per segment, from its <template> when present.
FADER_TAGSconstThe names the fader's elements are registered under, frozen: a page cannot rename them by accident.
FaderChangeEventtypeFired once a gesture ends with a new value.
FaderControlElementclassThe interactive area.
FaderElementclassThe root of a linear control, with audio defaults: vertical, from -60 to +6.
FaderElementEventMapinterfaceThe events a fader fires, as addEventListener types them.
FaderEventDetailinterfaceWhat a fader's input and change events carry, besides the fader itself as their target.
FaderIndicatorElementclassThe filled range of the track, from min to the value, or between anchor and the value.
FaderInputEventtypeFired on every change.
FaderLabelElementclassThe accessible label.
FaderThumbElementclassThe draggable thumb, positioned at the value.
FaderTrackElementclassThe full range of travel, containing the indicator and the thumb.
FaderValueElementclassRenders the value, formatted with the fader's format and locale.
KNOB_TAGSconstThe names the knob's elements are registered under, frozen: a page cannot rename them by accident.
KnobChangeEventtypeFired once a gesture ends with a new value.
KnobControlElementclassThe interactive surface.
KnobElementclassThe root of a rotary control.
KnobElementEventMapinterfaceThe events a knob fires, as addEventListener types them.
KnobEventDetailinterfaceWhat a knob's input and change events carry, besides the knob itself as their target.
KnobIndicatorElementclassThe arc from anchor to the value, rendered over the track.
KnobInputEventtypeFired on every change.
KnobLabelElementclassThe accessible label.
KnobThumbElementclassRotates with the value, clockwise from 12 o'clock, through rotate.
KnobTrackElementclassThe full sweep of the dial, rendered as an SVG arc in currentColor.
KnobValueElementclassRenders the value, formatted with the knob's format and locale.
METER_TAGSconstThe names the meter's elements are registered under, frozen: a page cannot rename them by accident.
MeterChannelElementclassA channel with role="meter", for its index, labelled by aria-label or an <audio-ui-meter-label>.
MeterChannelsElementclassGenerates an <audio-ui-meter-channel> per level from its <template>, regenerated when the channel count changes.
MeterElementclassThe root of a level meter, with role="group".
MeterIndicatorElementclassThe lit range of a channel, scaled to the level along the meter's length.
MeterLabelElementclassThe accessible label of its channel.
MeterMarkElementclassA scale graduation at value, such as -18 dB, placed along the meter's length from its minimum.
MeterPeakElementclassThe peak-hold marker at the channel's peak level.
MeterSnapshotinterfaceWhat the meter's parts draw from.
MeterSourceinterfaceA live signal, in the shape of an external store: subscribe calls back on each new frame, getSnapshot returns the latest.
MeterTrackElementclassThe full range of a channel, behind the indicator.
MeterValueElementclassRenders the channel's level, formatted with the meter's format and locale.
PAD_TAGSconstThe names the pad's elements are registered under, frozen: a page cannot rename them by accident.
PadElementclassThe root of a velocity-sensitive pad grid, with role="group".
PadElementEventMapinterfaceThe events a pad grid fires, as addEventListener types them.
PadItemElementclassA pad, a button that sounds while held, by pointer or with Enter and Space.
PadOffDetailinterfaceWhat a padoff event carries: the pad released, and why.
PadOffEventtypeFired when a pad stops sounding.
PadOnDetailinterfaceWhat a padon event carries: the pad struck, how hard, and how.
PadOnEventtypeFired when a pad is struck.
PIANO_TAGSconstThe names the piano's elements are registered under, frozen: a page cannot rename them by accident.
PianoElementclassThe root of a playable keyboard, with role="group".
PianoElementEventMapinterfaceThe events a piano fires, as addEventListener types them.
PianoKeyElementclassA key, positioned for its pitch.
PianoKeyLabelElementclassThe name of its key, such as "C4".
PianoKeysElementclassGenerates an <audio-ui-piano-key> per pitch, lowest first, filled from its <template>, such as with an <audio-ui-piano-key-label>.
PianoNoteOffDetailinterfaceWhat a noteoff event carries: the pitch released, and why.
PianoNoteOffEventtypeFired when a key stops sounding.
PianoNoteOnDetailinterfaceWhat a noteon event carries: the pitch played, how hard, and how.
PianoNoteOnEventtypeFired when a key starts sounding.
PIANO_ROLL_TAGSconstThe names the piano roll's elements are registered under, frozen: a page cannot rename them by accident.
PianoRollElementclassThe root of a note editor, placed inside an <audio-ui-timeline>, which provides its length, zoom, grid and position.
PianoRollElementEventMapinterfaceThe events a piano roll fires, as addEventListener types them.
PianoRollEventDetailinterfaceWhat a roll's input and change events carry, besides the roll itself as their target.
PianoRollGridElementclassThe note surface: a focusable timeline lane with role="group", with the rows stacked vertically.
PianoRollKeysElementclassLays out the <audio-ui-piano> it contains on the roll's rows, low end at the bottom, and lights the sounding pitches.
PianoRollMarqueeElementclassThe selection rectangle during a marquee drag.
PianoRollNoteElementclassA note, positioned at its start in timeline units and on its pitch's row.
PianoRollNoteHandleElementclassResizes its note from its edge (start or end): the end changes the length, the start trims from the front with the end fixed.
PianoRollNotesElementclassGenerates an <audio-ui-piano-roll-note> per note in reading order (by start, then highest pitch first), which is also the tab order, from its <template>, such as with <audio-ui-piano-roll-note-handle>s.
PianoRollRowElementclassThe band of a pitch across the grid, for row shading, scale highlighting and octave lines.
PianoRollRowsElementclassGenerates an <audio-ui-piano-roll-row> per row, highest pitch first, from its <template> when present.
PianoRollSelectionSpanElementclassThe time range of the selected notes, spanning the height of its container: the grid, the velocity lane or the ruler.
PianoRollStretchHandleElementclassAn edge of the selection span.
PianoRollVelocitiesElementclassGenerates an <audio-ui-piano-roll-velocity> per note, from its <template> when present.
PianoRollVelocityElementclassA note's velocity, or chance in a level="chance" lane, rendered as a bar sized by --piano-roll-level.
PianoRollVelocityLaneElementclassA timeline lane of per-note bars for velocity or, with level="chance", probability, each at its note's start.
PLOT_TAGSconstThe names the plot's elements are registered under, frozen: a page cannot rename them by accident.
PlotBandElementclassThe range of one axis between from and to, spanning the other axis, such as a sweep range or a level above a threshold.
PlotCursorElementclassTracks a mouse or pen over the plot, for crosshairs and readouts.
PlotCursorReadoutElementclassRenders the value under the pointer as text, inside an <audio-ui-plot-cursor>.
PlotCurveElementclassPlots y = fn(x) across the plot in axis units, from the fn property, such as a frequency response in dB.
PlotDotElementclassA non-interactive point, such as the input level on a compressor curve.
PlotDotSourcetypeA live point, in the axes' units.
PlotElementclassThe root of a plot.
PlotGridElementclassGrid lines and labels at round values along the axes.
PlotHandleChangeEventtypeFired once a gesture that moved a handle ends, and after each key or input change.
PlotHandleElementclassA draggable point in axis units, such as an equalizer band or a filter's cutoff and resonance.
PlotHandleElementEventMapinterfaceThe events a handle fires, as addEventListener types them.
PlotHandleEventDetailinterfaceWhat a handle's input and change events carry, besides the handle itself as their target.
PlotHandleInputEventtypeFired on every move of a handle.
PlotLineElementclassA straight line at value: vertical on the x axis (axis="x", the default), such as a cutoff, or horizontal on the y axis, such as 0 dB.
PlotMarkElementclassA label at value on one axis, such as "1k" or "+12", positioned with left (x, the default) or top (y).
PlotSnapshotinterfaceWhat every part draws from: the plot's two axes, and whether it is disabled.
PlotTraceElementclassA live line from the y values of the source property, such as a spectrum, spread evenly across the x axis and redrawn on each change.
PlotTraceSourcetypeA live list of y values, spread evenly across the x axis.
SEQUENCER_TAGSconstThe names the sequencer's elements are registered under, frozen: a page cannot rename them by accident.
SequencerCellElementclassA step: a toggle button, pressed while the step is on (data-on).
SequencerChangeEventtypeFired when a press, a paint or a key press that changed the pattern ends.
SequencerElementclassThe root of a step sequencer, one row per sound, with role="group".
SequencerElementEventMapinterfaceThe events a sequencer fires, as addEventListener types them.
SequencerEventDetailinterfaceWhat a sequencer's input and change events carry.
SequencerInputEventtypeFired while cells are flipped.
SequencerRowElementclassA row of steps with role="group", for its index from the top.
SequencerRowsElementclassGenerates an <audio-ui-sequencer-row> per pattern row from its <template>.
SequencerSourceinterfaceA live value followed without rendering: here, the step being played.
SequencerStepElementclassA generated step, for its index.
SequencerStepsElementclassGenerates an <audio-ui-sequencer-step> per step of its row from its <template>: a cell, a velocity bar, or both.
SequencerVelocityChangeEventtypeFired when a drag, a key press or a reset that changed a velocity ends.
SequencerVelocityDetailinterfaceWhat a sequencer's velocityinput and velocitychange events carry.
SequencerVelocityElementclassA step's velocity, rendered as a bar sized by --sequencer-velocity.
SequencerVelocityInputEventtypeFired while a velocity is drawn or nudged.
TimelineClipElementclassA region of a source on a lane, such as a recording or a pattern.
TimelineClipElementEventMapinterfaceThe events a clip fires, as addEventListener types them.
TimelineClipEventDetailinterfaceWhat a clip's events carry, besides the clip itself as their target.
TimelineClipFadeElementclassRenders the fade at its edge (in or out) over the clip: the gain ramp as a line in currentColor, with the attenuated area shaded by --clip-fade-shade (the line color by default).
TimelineClipFadeHandleElementclassSets the length of the fade at its edge (in or out) by drag, off the grid, or through the native <input type="range"> it contains, which carries the accessible name.
TimelineClipHandleElementclassTrims the clip's edge (start or end) by drag, on the grid unless Cmd or Ctrl is held, or through the native <input type="range"> it contains, which carries the accessible name.
TimelineClipHeaderElementclassThe clip's title bar.
TimelineLaneElementclassA lane for placed parts, such as a track's clips or a row of markers.
TimelineLaneElementEventMapinterfaceThe events a selectable lane or ruler fires, as addEventListener types them.
TimelineMarkElementclassA part placed at value on a lane, such as a bar number, a cue or a grid line, labelled by its content.
TimelineMarkElementEventMapinterfaceThe events a mark fires, as addEventListener types them.
TimelineNavigatorElementclassAn overview bar of the whole timeline, showing the visible range as <audio-ui-timeline-navigator-window>.
TimelineNavigatorHandleElementclassAn edge of the navigator window (start or end).
TimelineNavigatorWindowElementclassThe visible range of the timeline.
TimelinePlayheadElementclassA marker at the playback position on its lane, following it through --timeline-offset.
TimelineRulerElementclassThe lane that sets the position: a press seeks to the pointer and a drag scrubs, on the grid unless Cmd or Ctrl is held, four times finer with Shift.
TimelineViewportElementclassThe scrolling container of the timeline.
TimelineRangeElementclassA loop or selection on its lane, from value ("start end"; the whole timeline by default).
TimelineRangeElementEventMapinterfaceThe events a range fires, as addEventListener types them.
TimelineRangeEventDetailinterfaceWhat a range's events carry: which edge moved, or "both", and the range, [start, end] in the timeline's units.
TimelineRangeHandleElementclassResizes the edge (start or end) of its range by drag, on the grid unless Cmd or Ctrl is held, or through the native <input type="range"> it contains, which carries the accessible name.
TIMELINE_TAGSconstThe names the timeline's elements are registered under, frozen: a page cannot rename them by accident.
TimelineChangeEventtypeFired once a gesture that moved the position ends.
TimelineDescribetypeReturns how a position is announced.
TimelineElementclassThe root of a timeline.
TimelineElementEventMapinterfaceThe events a timeline fires, as addEventListener types them.
TimelineEventDetailinterfaceWhat a timeline's input and change events carry, besides the timeline itself as their target.
TimelineInputEventtypeFired on every move of the position.
TimelineSettingsinterfaceWhat the timeline's parts measure and step by, as its attributes set them.
TimelineZoomEventtypeFired when the zoom changes, already clamped.
ViewportMetricsinterfaceHow the viewport is scrolled and zoomed, as last laid out.
TimelineTrackElementclassA track: a row with a header and a lane.
TimelineTrackElementEventMapinterfaceThe events a track fires, as addEventListener types them: its height, in pixels.
TimelineTrackHeaderElementclassThe header beside a track's lane, for its name and controls.
TimelineTrackResizeHandleElementclassSets the track's height by drag, or through the native <input type="range"> it contains, which carries the accessible name: the arrow keys step (Down is taller), Page Up and Page Down take larger steps, and Home and End go to the minimum and maximum.
WAVEFORM_TAGSconstThe names the waveform's elements are registered under, frozen: a page cannot rename them by accident.
WaveformBarElementclassA bar of <audio-ui-waveform-bars>, for its index.
WaveformBarsElementclassRenders the waveform as count <audio-ui-waveform-bar> elements (24) for CSS-styled visualizers.
WaveformCanvasElementclassRenders the waveform on a canvas aligned to the device pixel grid, so it stays sharp at any density.
WaveformChangeEventtypeFired once a seek that moved the position ends.
WaveformControlElementclassThe seekable surface: a press seeks to the pointer and a drag scrubs.
WaveformControlElementEventMapinterfaceThe events a selectable waveform control fires, as addEventListener types them.
WaveformDescribetypeReturns how a position is announced.
WaveformElementclassThe root of a waveform.
WaveformElementEventMapinterfaceThe events a waveform fires, as addEventListener types them.
WaveformEventDetailinterfaceWhat a waveform's input and change events carry, besides the waveform itself as their target.
WaveformHoverElementclassShown at the hovered position while the pointer is over the control.
WaveformInputEventtypeFired on every seek.
WaveformLevelsSourcetypeA live signal: the current levels, 0…1 each, or raw samples for the scope variant.
WaveformMarkerElementclassA cue or chapter marker at value, labelled by its content.
WaveformPlayheadElementclassA non-interactive marker at the playback position.
WaveformPositionSourcetypeA live playback position, in duration units.
WaveformRangeDetailinterfaceWhat a selection's and a region's events carry: the stretch, [start, end] in duration units.
WaveformRegionElementclassA loop or selection over the waveform, from value ("start end" in duration units; the whole audio by default).
WaveformRegionElementEventMapinterfaceThe events a region fires, as addEventListener types them.
WaveformRegionHandleElementclassResizes the edge (start or end) of its region by drag or through the native <input type="range"> it contains, which receives its aria-label.
WaveformThumbElementclassA handle centered on the playback position, through --waveform-position.
XYPAD_TAGSconstThe names the XY pad's elements are registered under, frozen: a page cannot rename them by accident.
XYPadChangeEventtypeFired once a gesture ends with a new point.
XYPadControlElementclassThe interactive surface.
XYPadElementclassThe root of a two-dimensional control.
XYPadElementEventMapinterfaceThe events an XY pad fires, as addEventListener types them.
XYPadEventDetailinterfaceWhat an XY pad's input and change events carry, besides the pad itself as their target.
XYPadInputEventtypeFired on every change.
XYPadLabelElementclassThe accessible label.
XYPadThumbElementclassMarks the point, positioned with left and top and centered with translate.
XYPadValueElementclassRenders the point as "x, y", formatted with the pad's format and locale.
Built by Ouest Labs, the source code is available on GitHub.
RSS