Brik Design System
Components

MediaTabs

Peer tabs driving a synced media panel — the home Services and Industries bands.

MediaTabs renders a set of peer categories as a TabBar rail; the active tab reveals its description and crossfades its media into a synced panel. It auto-advances while in view, pauses on hover and focus, and yields to a click or keypress.

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

MediaTabs

Use when: Peer categories, each with its own media — the home Services / Industries bands.

Don't: An ordered sequence (a process), or peers with no media to sync.

<MediaTabs tabs={...} />

SyncedMediaSteps

Use when: An ordered sequence (a process) where each step drives its own media.

Don't: Peer categories the reader picks between — that's MediaTabs.

<SyncedMediaSteps steps={...} />

TabBar

Use when: Peer categories with no media panel to sync.

Don't: Each tab needs a synced media panel — that's MediaTabs.

<TabBar items={...} />

Use it for

  • The home "Services" (Marketing / Back-office) and "Industries" (Dental / Real Estate / Small Business) bands — switching between peer categories, each with its own media
  • Any tabbed band where the reader should be able to sit still and watch it advance, or take over with the mouse or keyboard

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

Import

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

Default

Campaigns, content, and the site that carries them.

Intake, scheduling, billing — wired so the front of house can move.

Marketing

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 tab and defaults to the --duration-autoplay token (10s). It is deliberately one value read by two consumers: the progress cue animates over it in CSS, and the component reads the resolved custom property back to set its advance timer — so the cue cannot drift away from the advance.

<MediaTabs tabs={tabs} interval="6s" />

Accessibility

  • Keyboard contract is the WAI-ARIA tabs pattern, delegated to TabBar. The rail exposes exactly one tab in the page Tab sequence (roving tabindex); Arrow / Home / End move focus and activate — Left/Right when horizontal, Up/Down when orientation="vertical".
  • Each tab is a real role="tab" <button> carrying aria-selected and aria-controls; each panel is a role="tabpanel" labelled by its tab, hidden while inactive.
  • TabBar gains this contract only when its items carry controls, so existing label-only tab bars keep their current behaviour.
  • The progress cue 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 progress cue is hidden, and the media swap and description reveal are instant. The active tab 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
tabsMediaTabData[]— (required)
activeTabstring— (uncontrolled)
onActiveTabChange(id: string) => void—
defaultActiveTabstringfirst tab's id
autoplaybooleantrue
intervalnumber | string— (falls back to --bds-media-tabs-interval, 10s)
pauseOnHoverbooleantrue
orientation'horizontal' | 'vertical''vertical'
mediaPosition'start' | 'end''end'
variant'text' | 'text-underline' | 'tab' | 'box''tab'
showProgressbooleantrue

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

Each entry in tabs is a MediaTabData: { id: string; label: string; description?: ReactNode; media: ReactNode; disabled?: boolean }.

On this page

💬