Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelogApp ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbarAnchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree ViewAccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual ListActivity HeatmapAnimated NumberBadgeBar ListChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveformBlockquoteCode BlockCode GroupGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriterButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerField ListFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree SelectAlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip
ARC UIARC Radiant Components
v4.9DocsComponentsTokensSynthesizer
Getting StartedFrameworksServer RenderingDesign TokensThemingTheme SynthesizerTypographyUtilitiesAll ComponentsAccessibilityBrowser SupportChangelogContributingStatsApp ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbarAnchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree ViewAccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual ListActivity HeatmapAnimated NumberBadgeBar ListChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveformBlockquoteCode BlockCode GroupGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriterButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerField ListFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree SelectAlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip

Waveform

Audio waveform visualization that doubles as a scrubber. Renders a consumer-computed peaks array as an SVG waveform with an accent played region and glowing playhead, and becomes a full seek control when interactive.

ComponentsWaveform
datahybrid
<arc-waveform>

Overview

Waveform draws an audio clip's shape from a plain array of peak amplitudes (one number per bar, 0 to 1) and marks playback progress on it: the played region renders in accent with a soft glow, the unplayed region stays muted, and a thin glowing playhead line sits at the current position. Set `interactive` and the same element becomes a scrubber, with pointer dragging, keyboard seeking, and full slider semantics for assistive technology. The component never touches audio: no AudioContext, no decoding, no fetching. The consumer computes peaks however it likes (from a decoded buffer, a server-side analysis pass, or a cached sidecar file) and hands them over as the `peaks` property. That split keeps the component a pure function of its props, so it server-renders, and keeps it cheap enough to repeat down a lane of clips in a DAW timeline or a list of voice memos. `position` is a fraction of the total (0 to 1), not seconds. If your player thinks in seconds, divide by the duration on the way in and multiply on the way out. The `time` field on both event details does the multiplication for you whenever `duration` is set. Setting `duration` also renders a monospaced time readout below the track and switches the slider's spoken value from a percentage to elapsed and total time. While scrubbing, `arc-input` fires on every pointer move with the live position, and `arc-change` fires once on release with the committed one. This is the standard ARC edit/commit contract. Wire the actual seek of your audio source to `arc-change`, and use `arc-input` for live feedback such as a time display or audible scrubbing. Two variants cover the common looks: `bars` draws discrete bar pairs mirrored around the center line, and `mirror` draws a filled min/max envelope.

Guidelines

When to use

  • Compute peaks once per clip and cache them. A few dozen to a few hundred values is plenty; the component clamps each to 0-1
  • Wire the actual seek of your audio source to arc-change, and keep arc-input for cheap live feedback like a time display
  • Set duration whenever you know it, so users get time readouts and screen readers hear times instead of percentages
  • Give every waveform a label. It names the slider for assistive technology, or describes the image when not interactive
  • Use the bars variant for scrubbers and player UI, and the mirror variant for dense timeline lanes where discrete bars would shimmer
  • Set the height with the --waveform-height custom property when the default is too short or too tall for its row

When not to use

  • Do not use Waveform for generic trend data. That is Sparkline’s job; Waveform’s shape says "audio" and its center-mirrored geometry distorts ordinary series
  • Do not use it as a level indicator or progress bar. Meter shows a single current value against a range; Waveform shows amplitude over time
  • Do not feed it raw sample data. Downsample to peaks first; tens of thousands of bars help no one and cost real DOM
  • Do not track position in seconds. The position property is a 0-1 fraction; convert at the edges or read the time field on event details
  • Do not seek your audio source on arc-input. That fires on every pointer move; the committed value arrives once, on arc-change

Features

  • `steps` for a timeline of discrete frames (arrow keys move one step), and `value-text` for your own screen-reader wording
  • Renders from a consumer-computed peaks array: no AudioContext, no decoding, no audio dependencies
  • Two variants: `bars` (mirrored bar pairs) and `mirror` (filled min/max envelope)
  • Played region in accent with a soft glow; unplayed region muted; thin glowing playhead line
  • Interactive mode adds pointer scrubbing and keyboard seeking (arrows, PageUp/PageDown, Home/End)
  • Standard edit/commit events: `arc-input` continuously while scrubbing, `arc-change` once on release
  • Optional `duration` enables monospaced elapsed/total time readouts and spoken time values
  • Full slider ARIA when interactive; labeled image semantics otherwise
  • Resizes by viewBox scaling: no ResizeObserver, no measuring, fluid at any width
  • Playhead motion uses the motion tokens and honors prefers-reduced-motion
  • Server-renders: the SVG is a pure function of props
  • Empty or missing peaks render an empty track rather than an error

Preview

Bars, interactive — drag or use arrow keys to seek
Mirror envelope, static

Usage

Waveform is in the media domain group, so it is absent from the default @arclux/arc-ui barrel. Import it from@arclux/arc-ui/media, or from its own subpath@arclux/arc-ui/waveform. Everything else (the element, the CSS, the framework wrappers, the support it gets) is unchanged.

Layout and styling work without JavaScript via the HTML/CSS versions. Interactive features like events and state management require the Web Component or a framework wrapper.

<script type="module" src="@arclux/arc-ui"></script>

<!-- Interactive scrubber with time readouts -->
<arc-waveform id="scrubber" interactive duration="212" label="Track position"></arc-waveform>

<!-- Static clip preview, mirror envelope -->
<arc-waveform id="clip" variant="mirror" position="0.6" label="Vocal take"></arc-waveform>

<script type="module">
  const scrubber = document.getElementById('scrubber');

  // Peaks are a property, not an attribute — you compute them (0..1 per bar).
  scrubber.peaks = await fetchPeaks('track-7');

  scrubber.addEventListener('arc-input', (e) => {
    // Live position while dragging: e.detail.value is 0..1,
    // e.detail.time is seconds because duration is set.
    timeDisplay.textContent = format(e.detail.time);
  });
  scrubber.addEventListener('arc-change', (e) => {
    audio.currentTime = e.detail.time; // seek once, on release
  });

  // Follow playback
  audio.addEventListener('timeupdate', () => {
    scrubber.position = audio.currentTime / audio.duration;
  });
</script>

API

labelstring''
Accessible name for the waveform. Announced as the slider label when interactive, or as the image description otherwise.
peaksArray[]
Peak amplitudes as a number array, each value 0 to 1. Set it from script, a framework binding, or a JSON attribute. Values outside the range are clamped. An empty or missing array renders an empty track.
positionnumber0
Current playhead position as a fraction of the total, 0 to 1. Not seconds: multiply by duration yourself if you track seconds. Updated by the component during scrubbing and reflected as an attribute.
durationnumber0
Total duration in seconds. Optional; when set, time readouts render below the waveform and the slider announces times instead of percentages.
interactivebooleanfalse
Enables scrubbing. The waveform becomes a focusable slider: click or drag to seek, arrow keys to nudge, Home/End to jump. Without it the waveform is a static image.
variant'bars' | 'mirror''bars'
Rendering style. bars draws discrete bar pairs mirrored around the center line; mirror draws a filled min/max envelope.
stepsnumber0
Divides the track into this many equal steps, for a timeline of discrete frames rather than continuous audio. The playhead snaps to a step from pointer and keyboard alike, arrow keys move one step, Page Up/Down a tenth of the track (at least one step), and the slider reports the step number rather than a percentage. Unset, the track is continuous.
value-textstring
What the slider announces for the current position, replacing the built-in percentage or time. Set it from your arc-input handler, which runs before the position renders, so the text and the value always arrive together (e.g. "85 of 200 ms").

Events

arc-inputdetail:{ value: number, time: number | null }
Fired continuously while scrubbing (every pointer move and each keyboard nudge). value is the position fraction 0-1; time is seconds when duration is set, otherwise null. Use for live preview: updating a time display, or audibly scrubbing.
arc-changedetail:{ value: number, time: number | null }
Fired once when the seek commits: on pointer release, or with each keyboard nudge. Use for the actual seek on your audio source.

See Also