From c9ba1e1bfc83e02981a2ffad0a0f247092068687 Mon Sep 17 00:00:00 2001 From: rahim Date: Thu, 12 Feb 2026 18:12:55 +1100 Subject: [PATCH] feat(core): add poster component (#457) --- internal/design/ui/poster.md | 208 ++++++++++++++++++ packages/core/src/core/index.ts | 2 + .../core/src/core/ui/poster/poster-core.ts | 17 ++ .../src/core/ui/poster/poster-data-attrs.ts | 6 + .../core/ui/poster/tests/poster-core.test.ts | 73 ++++++ packages/html/src/define/ui/poster.ts | 9 + packages/html/src/index.ts | 1 + packages/html/src/ui/poster/poster-element.ts | 34 +++ packages/react/src/index.ts | 1 + packages/react/src/ui/poster/poster.tsx | 58 +++++ 10 files changed, 409 insertions(+) create mode 100644 internal/design/ui/poster.md create mode 100644 packages/core/src/core/ui/poster/poster-core.ts create mode 100644 packages/core/src/core/ui/poster/poster-data-attrs.ts create mode 100644 packages/core/src/core/ui/poster/tests/poster-core.test.ts create mode 100644 packages/html/src/define/ui/poster.ts create mode 100644 packages/html/src/ui/poster/poster-element.ts create mode 100644 packages/react/src/ui/poster/poster.tsx diff --git a/internal/design/ui/poster.md b/internal/design/ui/poster.md new file mode 100644 index 00000000..22d70a76 --- /dev/null +++ b/internal/design/ui/poster.md @@ -0,0 +1,208 @@ +--- +status: draft +date: 2025-02-05 +--- + +# Poster + +Display component for video poster image. Shows before playback starts, hides after. + +## Problem + +Video players show a poster image before playback. The poster: + +1. Gives users a preview of the content +2. Should hide once playback starts +3. May optionally reappear when playback ends + +Existing solutions (Media Chrome, Vidstack) either: + +- Manage the image internally via `src` prop +- Expose complex state (`data-loading`, `data-error`, `data-hidden`, `data-visible`) + +We want a simpler approach: expose minimal state, let the user control the image. + +## Solution + +Minimal components that: + +1. Expose `data-visible` for CSS-based show/hide +2. Do nothing else + +**HTML:** Wrapper element that accepts `` as child. +**React:** Renders `` directly — no wrapper needed. + +### Usage + +#### HTML + +```html + + Video description + +``` + +#### React + +```tsx +import { Poster } from '@videojs/react'; + +; +``` + +#### CSS + +```css +media-poster:not([data-visible]) { + display: none; +} +``` + +### With Responsive Image + +#### HTML + +```html + + Video description + +``` + +#### React + +```tsx + +``` + +User controls the image entirely — responsive images, lazy loading, placeholder strategies all work naturally. + +### With Placeholder (Blurhash / LQIP) + +Use CSS `background-image` on the `` element to show a placeholder while the main image loads. When the `src` loads, it naturally covers the background. + +#### HTML + +```html + + Video description + +``` + +#### React + +```tsx + +``` + +For fade transitions (blur → sharp), handle the `load` event on the image: + +```html + + Video description + +``` + +## API + +### Data Attributes + +| Attribute | Description | +| -------------- | -------------------------------------------- | +| `data-visible` | Present when poster should show (`!started`) | + +### Visibility Logic + +```ts +visible = !playback.started; +``` + +The poster is visible until playback has started. Once `started` becomes `true` (user plays or seeks), the poster hides and stays hidden. + +**Note:** `started` persists — pausing doesn't reset it. The poster only shows on initial load or after a new source is loaded. + +## Styling Notes + +The component sets no default styles. Recommended CSS: + +```css +media-poster { + position: absolute; + inset: 0; + pointer-events: none; +} + +media-poster:not([data-visible]) { + display: none; +} + +media-poster img { + width: 100%; + height: 100%; + object-fit: contain; +} +``` + +**`pointer-events: none`** — Clicks should pass through to the video or play button beneath. + +## Accessibility + +**Wrapper (``):** No ARIA role needed. Custom elements have no implicit role, so there's no semantics to hide or override. Do not add `aria-hidden` to the wrapper — the poster image may be informative. + +**Child (``):** User provides appropriate `alt` text describing the poster content (e.g., `alt="Keynote speaker at a conference"`). If purely decorative, use `alt=""`. + +Whether a poster is informative or decorative is the author's judgment (per [WAI guidelines](https://www.w3.org/WAI/tutorials/images/decorative/)). In most video player contexts, posters are informative — they give users a preview of the video content. This is an advantage over Media Chrome (which forces `aria-hidden="true"` on the internal image) and native `