Brik Design System
Components

SyncedMediaSteps

Accordion-style step sequence driving a synced media panel — process and how-it-works bands.

SyncedMediaSteps renders an ordered sequence of steps as an <ol> of accordion rows. The active row reveals its description and crossfades its media into a panel beside the list. It auto-advances while in view, pauses on hover and focus, and yields to a click.

It has one visual appearance — mediaPosition, autoplay, pauseOnHover, and showStepNumbers are behavior and placement knobs, not distinct variants.

SyncedMediaSteps

Use when: An ordered sequence (a process), each step with its own media — how-it-works bands.

Don't: Peer categories, or a sequence with no media to sync.

<SyncedMediaSteps steps={...} />

MediaTabs

Use when: Peer categories the reader picks between, each with its own media.

Don't: The items are an ordered sequence — that's SyncedMediaSteps.

<MediaTabs tabs={...} />

Accordion

Use when: An ordered or choose-among sequence with no media to sync.

Don't: Each step needs a synced media panel — that's SyncedMediaSteps.

<Accordion items={...} />

Use it for

  • A "how it works" or process band where the steps are ordered and each one has its own visual
  • Any sequence where the reader should be able to sit still and watch it advance, or take over and click

The sequence-vs-peer split is the load-bearing choice, and it drives a different ARIA contract (the accordion here keeps every trigger in the Tab sequence; MediaTabs uses a roving tabindex) — see Accessibility. Reach for a plain Accordion when there is no media to sync.

Import

import { SyncedMediaSteps } from '@brikdesigns/bds';

Default

  1. Map the surfaces and agree what ships first.

  2. Tokens, then components, then pages.

Scope

Media is the consumer's responsibility for alt text, loading="lazy", and video poster frames. Inactive media stays mounted so the crossfade has both frames to work with.

Timing

interval sets the dwell time per step and defaults to the --duration-autoplay token (10s). It is deliberately one value read by two consumers: the countdown bar animates over it in CSS, and the component reads the resolved custom property back to set its advance timer — so the bar cannot drift away from the advance.

<SyncedMediaSteps steps={steps} interval="6s" />

Accessibility

  • Keyboard contract is the accordion pattern, not tabs. Every step trigger is a real <button> in the page Tab sequence, per the WAI-ARIA APG accordion pattern — "all focusable elements in the accordion are included in the page Tab sequence". A roving tabindex belongs to the tabs pattern.
  • Each trigger carries aria-expanded and aria-controls; each panel is a region labelled by its trigger.
  • Collapsed descriptions and inactive media use visibility: hidden, so they leave both the accessibility tree and the tab order rather than staying readable while invisible.
  • The countdown bar is aria-hidden — it is a dwell cue, not a measurement, and carries no progressbar role.
  • Under prefers-reduced-motion: reduce autoplay is disabled in JS, the countdown is hidden, and the media swap and description reveal are instant. The active step still reads correctly, statically.
  • The component provides no accessible name of its own; wrap it with a heading in the surrounding page, same as any other content band.

API

PropTypeDefault
stepsSyncedMediaStepData[]— (required)
activeStepstring— (uncontrolled)
onActiveStepChange(id: string) => void—
defaultActiveStepstringfirst step's id
autoplaybooleantrue
intervalnumber | string— (falls back to --bds-synced-media-steps-interval, 10s)
pauseOnHoverbooleantrue
mediaPosition'start' | 'end''end'
showStepNumbersbooleantrue
showCountdownbooleantrue

Inherits all native <div> attributes (className, style, data-*, …).

Each entry in steps is a SyncedMediaStepData: { id: string; title: ReactNode; description?: ReactNode; media: ReactNode }.

On this page

💬