import { Media } from "@elabs-ai/components-ui";npm install @elabs-ai/components-tokens @elabs-ai/components-uiPrompt for your coding agent
Paste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.
Add the brand-ui Media component (from @elabs-ai/components-ui) to this project. 1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`. 2. Look Media up before writing code: `npx -y @elabs-ai/components-cli docs Media` prints its real props, variants, what it pairs with and what to avoid. 3. If @elabs-ai/components-ui is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`. 4. Use it here: [the screen or file, and what it should show] 5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop. 6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.
Examples
Video File
The same file forced to play as video — the picture chrome a real video gets by detection.
Cover Art
A podcast episode: the poster stays as cover art, the waveform runs along its bottom.
Real Peaks
The app decoded the file and passed its `peaks`: the bars draw the real recording.
With Captions
Captions still render for sound — a transcript track is the text alternative.
Forced Kind
`kind="audio"` skips detection: the waveform shows before the file loads.
Reduced motion
Under reduced motion the bars still fill as it plays, but nothing pulses.
API
Media
| Prop | Type | Description |
|---|---|---|
kind | "audio" | "video" | What the file holds. Omit to detect it: once the metadata loads, a file with no picture plays as sound. Pass it to skip the wait (the waveform shows before the file loads) or to overrule the file. |
peaks | readonly number[] | A sound file's shape, 0–1, any length — the app supplies it (e.g. from `AudioBuffer.getChannelData`); the player never fetches or decodes. Omit for a stand-in shape drawn from `src`. |
aspectRatio | number | Width ÷ height of the viewport (e.g. `16 / 9`). Omit to size by the video. |
controls | boolean | Render the default control bar. Default `true`. |
controlsPlacement | MediaPlayerControlsPlacement | `docked` (default): an opaque bar under the video · `overlay`: floats over it and auto-hides. Sound always docks — there is no picture to reveal. |
tracks | MediaTrack[] | Caption / subtitle tracks. Give every non-decorative video a captions track. |
children | ReactNode | A full custom composition of `MediaPlayer*` parts, rendered after the viewport; when given, the default bar is not rendered. For sound the viewport's waveform is already the scrubber — leave `MediaPlayerTimeSlider` out. |
MediaPlayer
| Prop | Type | Description |
|---|---|---|
kindrequired | MediaKind | Which element `MediaPlayerElement` renders. |
label | string | The region's accessible name. Default "Audio player" / "Video player". |
keyboardShortcuts | boolean | Handle the player keyboard map (Space/k, ←/→, j/l, ↑/↓, m, f, 0–9). Default `true`. |
MediaPlayerCaptionsButton
| Prop | Type | Description |
|---|---|---|
size | IconButtonSize | Icon-only size axis. Default `"icon"`. |
disabledReason | string | Shown appended to the tooltip text, and — independently — exposed via `aria-describedby` through an `sr-only` node that is present whenever this prop is (i.e. it does NOT depend on the tooltip being open, and it is not rendered at all when there is no reason — so it can never be read as stray text), so assistive tech gets the reason even while the button is disabled (a disabled `<button>` still exposes its ARIA attributes; it just isn't focusable/hoverable itself, which is why the tooltip trigger wraps a focusable `<span>` instead — see the render below). |
side | ComponentProps<typeof TooltipContent>["side"] | Tooltip placement. Default `"top"`. |
MediaPlayerControls
MediaPlayerElement
MediaPlayerError
| Prop | Type | Description |
|---|---|---|
title | ReactNode | Heading text. |
description | ReactNode | Supporting description below the title. |
titleAs | "div" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | Heading level for the panel's title (#328 convention). Set this to the level that correctly follows whatever heading precedes this panel in the document outline — e.g. `"h3"` inside a `ChartCard` whose own title is an `<h2>`. @default "h3" |
size | "sm" | "md" | "lg" | Spinner size (loading kind only). @default "md" |
actions | ReactNode | Primary / secondary action buttons. |
MediaPlayerFullscreenButton
| Prop | Type | Description |
|---|---|---|
size | IconButtonSize | Icon-only size axis. Default `"icon"`. |
disabledReason | string | Shown appended to the tooltip text, and — independently — exposed via `aria-describedby` through an `sr-only` node that is present whenever this prop is (i.e. it does NOT depend on the tooltip being open, and it is not rendered at all when there is no reason — so it can never be read as stray text), so assistive tech gets the reason even while the button is disabled (a disabled `<button>` still exposes its ARIA attributes; it just isn't focusable/hoverable itself, which is why the tooltip trigger wraps a focusable `<span>` instead — see the render below). |
side | ComponentProps<typeof TooltipContent>["side"] | Tooltip placement. Default `"top"`. |
MediaPlayerLoading
MediaPlayerMuteButton
| Prop | Type | Description |
|---|---|---|
size | IconButtonSize | Icon-only size axis. Default `"icon"`. |
disabledReason | string | Shown appended to the tooltip text, and — independently — exposed via `aria-describedby` through an `sr-only` node that is present whenever this prop is (i.e. it does NOT depend on the tooltip being open, and it is not rendered at all when there is no reason — so it can never be read as stray text), so assistive tech gets the reason even while the button is disabled (a disabled `<button>` still exposes its ARIA attributes; it just isn't focusable/hoverable itself, which is why the tooltip trigger wraps a focusable `<span>` instead — see the render below). |
side | ComponentProps<typeof TooltipContent>["side"] | Tooltip placement. Default `"top"`. |
MediaPlayerPipButton
| Prop | Type | Description |
|---|---|---|
size | IconButtonSize | Icon-only size axis. Default `"icon"`. |
disabledReason | string | Shown appended to the tooltip text, and — independently — exposed via `aria-describedby` through an `sr-only` node that is present whenever this prop is (i.e. it does NOT depend on the tooltip being open, and it is not rendered at all when there is no reason — so it can never be read as stray text), so assistive tech gets the reason even while the button is disabled (a disabled `<button>` still exposes its ARIA attributes; it just isn't focusable/hoverable itself, which is why the tooltip trigger wraps a focusable `<span>` instead — see the render below). |
side | ComponentProps<typeof TooltipContent>["side"] | Tooltip placement. Default `"top"`. |
MediaPlayerPlayButton
| Prop | Type | Description |
|---|---|---|
size | IconButtonSize | Icon-only size axis. Default `"icon"`. |
disabledReason | string | Shown appended to the tooltip text, and — independently — exposed via `aria-describedby` through an `sr-only` node that is present whenever this prop is (i.e. it does NOT depend on the tooltip being open, and it is not rendered at all when there is no reason — so it can never be read as stray text), so assistive tech gets the reason even while the button is disabled (a disabled `<button>` still exposes its ARIA attributes; it just isn't focusable/hoverable itself, which is why the tooltip trigger wraps a focusable `<span>` instead — see the render below). |
side | ComponentProps<typeof TooltipContent>["side"] | Tooltip placement. Default `"top"`. |
MediaPlayerPlaybackRateMenu
| Prop | Type | Description |
|---|---|---|
rates | readonly number[] | Offered speeds. Default `[0.5, 0.75, 1, 1.25, 1.5, 2]`. |
MediaPlayerSeekButton
| Prop | Type | Description |
|---|---|---|
offsetrequired | number | Seconds to jump; negative seeks backward. |
size | IconButtonSize | Icon-only size axis. Default `"icon"`. |
disabledReason | string | Shown appended to the tooltip text, and — independently — exposed via `aria-describedby` through an `sr-only` node that is present whenever this prop is (i.e. it does NOT depend on the tooltip being open, and it is not rendered at all when there is no reason — so it can never be read as stray text), so assistive tech gets the reason even while the button is disabled (a disabled `<button>` still exposes its ARIA attributes; it just isn't focusable/hoverable itself, which is why the tooltip trigger wraps a focusable `<span>` instead — see the render below). |
side | ComponentProps<typeof TooltipContent>["side"] | Tooltip placement. Default `"top"`. |
MediaPlayerTime
| Prop | Type | Description |
|---|---|---|
mode | "current" | "duration" | "remaining" | `current` playhead · `duration` total · `remaining` time left (prefixed "-"). Default `current`. |