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
divelements: 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
| Key | Behaviour |
|---|
API
AspectRatio
Renders Radix AspectRatio.Root and passes it every other prop.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | Render the child element as the inner box instead of a div. | |
ratio | number | The 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.
| Token | Used for |
|---|