Skip to the content

ComponentsMedia

Aspect ratio

Keeps its content at a fixed width-to-height ratio as the width changes, such as a 16:9 image or video.

Import

import { AspectRatio } from "@booleanpress/ui/aspect-ratio"

Usage

An aspect ratio box reserves the right height before its image or video loads, so the page does not jump.

import { AspectRatio } from "@booleanpress/ui/aspect-ratio"

export function Screenshot() {
  return (
    <AspectRatio ratio={16 / 9}>
      <img src="/screenshots/dashboard.png" alt="The delivery dashboard" className="size-full object-cover" />
    </AspectRatio>
  )
}

ratio is the width divided by the height: 16 / 9, 4 / 3, 1 for a square. The box takes the width of its parent and draws nothing of its own; give it a radius, an edge or a fill with className, and overflow-hidden to clip a rounded image. Its child fills it: use object-cover on an image to crop it, object-contain to letterbox it.

Examples

16:9 image

A screenshot cropped to 16:9 in a rounded, bordered box.

import { AspectRatio } from "@booleanpress/ui/aspect-ratio"

// A drawn chart stands in for a screenshot, so the example needs no network.
const SCREENSHOT =
  "data:image/svg+xml;utf8," +
  encodeURIComponent(
    '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 90"><rect width="160" height="90" fill="#f1f5f9"/><rect x="10" y="10" width="60" height="6" rx="2" fill="#cbd5e1"/><path d="M10 74 L40 52 L64 60 L92 34 L120 42 L150 20" fill="none" stroke="#020617" stroke-width="2"/><path d="M10 74 L40 52 L64 60 L92 34 L120 42 L150 20 L150 80 L10 80 Z" fill="#020617" fill-opacity="0.08"/></svg>'
  )

export default function AspectRatioImage() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={16 / 9} className="overflow-hidden rounded-lg border">
        <img src={SCREENSHOT} alt="Deliveries per day over the last week, rising" className="size-full object-cover" />
      </AspectRatio>
    </div>
  )
}

Square

ratio={1}: three square tiles in a grid.

import { AspectRatio } from "@booleanpress/ui/aspect-ratio"

const LOGOS = ["Acme", "Globex", "Initech"]

export default function AspectRatioSquare() {
  return (
    <div className="grid w-full max-w-sm grid-cols-3 gap-3">
      {LOGOS.map((name) => (
        <AspectRatio key={name} ratio={1} className="flex items-center justify-center rounded-lg border bg-subtle">
          <span className="text-sm/normal font-semibold text-muted-foreground">{name}</span>
        </AspectRatio>
      ))}
    </div>
  )
}

Video placeholder

A 16:9 placeholder with a play button, ready for a video player.

import { PlayIcon } from "lucide-react"
import { AspectRatio } from "@booleanpress/ui/aspect-ratio"
import { Button } from "@booleanpress/ui/button"

export default function AspectRatioVideo() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={16 / 9} className="flex flex-col items-center justify-center gap-3 rounded-lg bg-muted">
        <Button size="icon-lg" className="rounded-full" aria-label="Play: connecting your first mail provider">
          <PlayIcon className="fill-current" />
        </Button>
        <span className="text-sm/normal text-muted-foreground">Connecting your first mail provider · 3:12</span>
      </AspectRatio>
    </div>
  )
}

Accessibility

Semantics
Two plain div elements: an outer one that sets the height and an inner one that holds the content. No role.
Labels
The content carries its own name: an image's alt, a video's title, a button's label.
Focus
Nothing to focus; the content's own controls are in the tab order.
Known limits
  • Content taller than the ratio allows is cut off or overflows; the box does not grow.

Keyboard

Keyboard
KeyBehaviour

API

AspectRatio

Renders Radix AspectRatio.Root and passes it every other prop.

AspectRatio props
PropTypeDefaultDescription
asChildbooleanRender the child element as the inner box instead of a div.
rationumberThe width divided by the height. 1 by default.

Every part takes className, merged with its defaults by cn(), and ref, which reaches the element it renders.

Data attributes: data-slot="aspect-ratio" (AspectRatio).

Theming

The tokens its classes read. Change their values in your theme, and it follows.

Theme tokens
TokenUsed for