Video Player

@sushindustries/ui

A video held behind a picture of itself. The player is a child, so it mounts on play and unmounts on stop.

Last updated Aug 17, 20267 min read
View as Markdown

Props

PropTypeDefaultDoes
titlestring-What the video is. Required, and used three times: the poster's alt text, the caption under the frame, and the button's accessible name - "Play" alone tells a screen-reader user nothing on a page with four of these.
provider?VideoProvider"file"Only ever a data attribute and a label. Nothing here behaves on it.
poster?string-The still. Without one the frame is a plain ground and the title.
variant?VideoVariant"inline"Rendered as data-variant, never a modifier class.
theme?VideoTheme"auto"Forces the dark frame instead of following the page. auto is omitted rather than written, because a page full of data-theme="auto" says nothing and the selector wants a theme somebody chose.
ratio?string"16 / 9"CSS aspect ratio for the reserved box, e.g. 16 / 9.
caption?ReactNode-A line under the frame. The title is already shown; this is the rest.
sourceLabel?string-Where it is hosted, shown on the frame: "YouTube", "Mux".
active?boolean-Controlled activation. Leave both out and the component owns its own state; pass them and the host can enforce one playing video per page.
onActiveChange?(active: boolean) => void-Fires in both modes, so a host can coordinate without taking ownership.
children?ReactNode-The real player. Mounted only while active, so stopping unmounts it.

Block attributes

The Markdown block takes the same set as strings:

AttributeMaps toNotes
providerproviderAnything other than youtube or mux is treated as file.
idthe playerThe YouTube id or the Mux playback id.
srcthe playerThe URL, for file. One of id or src is required; without either the block says so rather than rendering an empty frame.
titletitle
posterposterDefaults to the provider's own still.
variantvariant
ratioratio
captioncaption
captionsa <track>A WebVTT URL. file only: a cross-origin iframe cannot be given one.

State

Uncontrolled unless both active and onActiveChange are passed. The component reads active ?? ownActive, so a host that passes only the callback still gets told about every change while the component keeps its own state.

Accessibility

The poster is a <button> covering the whole frame, not a div with a click handler and not a small button floating over a picture: the whole still is the target, and the keyboard and the screen reader come for free. The wrapper is a <figure> with a <figcaption>, so the caption is associated with the media by the element choice rather than by an ARIA attribute.