Skip to content
goodtabs UI
BrowseBrowse components

Video frame

A framed native video with controls and a poster image.

npx shadcn@latest add https://ui.goodtabs.co/r/video-frame.json

With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/video-frame

A 16 by 9 video showing its poster before playback.

Square

The same frame at a 1 by 1 ratio.

Usage

import { VideoFrame } from "@/components/ui/video-frame"

<VideoFrame poster="/poster.jpg" title="Product walkthrough">
  <source src="/walkthrough.mp4" type="video/mp4" />
</VideoFrame>

Props

PropTypeDefaultDescription
posterstringPoster image shown before playback.
titlestringAccessible name for the video.
ratio"16/9" | "4/3" | "1/1"Aspect ratio of the frame. Defaults to 16/9.
maxWidthstringConstrain the frame, for example "560px".

Source

video-frame/video-frame.tsx
import * as React from "react";
import { cn } from "@/lib/utils";

type VideoFrameProps = Omit<React.ComponentProps<"video">, "title"> & {
  /** Poster image shown before playback. */
  poster: string;
  /** Accessible name for the video. */
  title: string;
  /** Constrain the frame, for example "560px". */
  maxWidth?: string;
  /** Aspect ratio of the frame. */
  ratio?: "16/9" | "4/3" | "1/1";
};

const ratios = { "16/9": "aspect-video", "4/3": "aspect-[4/3]", "1/1": "aspect-square" } as const;

/**
 * A framed native video. The browser supplies the controls, so keyboard and
 * screen reader support come for free. Pass sources as children.
 */
export function VideoFrame({
  poster,
  title,
  maxWidth,
  ratio = "16/9",
  className,
  style,
  children,
  ...props
}: VideoFrameProps) {
  return (
    <div
      className={cn("w-full overflow-hidden rounded-lg border bg-muted p-1", className)}
      style={{ maxWidth }}
    >
      <video
        controls
        preload="none"
        playsInline
        poster={poster}
        aria-label={title}
        className={cn("block w-full rounded-md bg-background object-cover", ratios[ratio])}
        style={style}
        {...props}
      >
        {children}
      </video>
    </div>
  );
}

For AI agents

Hand your agent this page, the markdown at /components/video-frame/prompt.md, or the Copy AI prompt button above.

When to use

  • A product walkthrough or short demo on a landing page
  • Any native video that needs a calm, bordered frame

When not to use

  • Embeds from a video host, which want an iframe
  • Autoplaying background video

Agent notes

  • Server component; the native video element supplies the controls
  • Pass sources and captions as children (source, track)
  • preload is none, so the poster is the only request until play
  • Other video props pass through to the element