AspectRatio
Reserves a fixed proportion of space before its content loads. Use it around every image, video, and map. Without it the page reflows when media arrives, which costs you layout stability and reads as a broken load.
Playground
Change a prop and watch it. The code below follows along.
Live
Open full cardInstall and import
AspectRatio is in @dovetail-ds/react. Install it with React, if you haven't:
npm install @dovetail-ds/react react react-dom
import { AspectRatio } from "@dovetail-ds/react";
Import @dovetail-ds/react/styles.css once at your app's root, then your theme after it. Setup covers the stylesheets, fonts and dark mode.
Guidelines
Reserves a fixed proportion of space before its content loads. Use it around every image, video, and map. Without it the page reflows when media arrives, which costs you layout stability and reads as a broken load.
Ratios
The system ships seven. Pick by role, not by taste.
| Ratio | Use for |
|---|---|
square | Avatars, product tiles, gallery grids |
4:3 | Editorial photography, screenshots |
3:2 | Standard camera output, blog headers |
16:9 | Video, hero imagery, cards. The default. |
21:9 | Full-bleed banners and page headers |
3:4 / 9:16 | Portrait and mobile-first placements |
A raw number is allowed for a one-off crop, but prefer a named ratio so a template stays consistent when its content changes.
Rules
Give it a width; it derives its own height. Never set a height on an AspectRatio; that defeats the reservation. Children are absolutely positioned against it, so a single child with inset: 0 fills it exactly. That is what Image does.
Props
The typed contract, from AspectRatio.d.ts.
import * as React from "react";
/** Reserves a fixed proportion of space before its content loads, so media never shifts the layout. */
export interface AspectRatioProps extends React.HTMLAttributes<HTMLElement> {
/** Named ratio from the system scale, or a raw width/height number. @default "16:9" */
ratio?: "square" | "4:3" | "3:2" | "16:9" | "21:9" | "3:4" | "9:16" | number;
/** Element to render. @default "div" */
as?: keyof React.JSX.IntrinsicElements;
children?: React.ReactNode;
}
export declare function AspectRatio(props: AspectRatioProps): React.JSX.Element;
Tokens it reads
None. AspectRatio.jsx resolves no custom property: it sets structure and nothing a theme can move.
Source
React only, no dependencies, every value a token.
import React from "react";
const RATIOS = { square: 1, "4:3": 4 / 3, "3:2": 3 / 2, "16:9": 16 / 9, "21:9": 21 / 9, "3:4": 3 / 4, "9:16": 9 / 16 };
export function AspectRatio({ ratio = "16:9", as: Tag = "div", children, style, ...rest }) {
const value = typeof ratio === "number" ? ratio : RATIOS[ratio] || RATIOS["16:9"];
return (
<Tag style={{ position: "relative", width: "100%", aspectRatio: String(value), overflow: "hidden", ...style }} {...rest}>
{children}
</Tag>
);
}
In context
AspectRatio also appears on the content reference cards, alongside the rest of its family.