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.

Content AspectRatio.jsx AspectRatio.d.ts AspectRatio.md

Playground

Change a prop and watch it. The code below follows along.

Install 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.

RatioUse for
squareAvatars, product tiles, gallery grids
4:3Editorial photography, screenshots
3:2Standard camera output, blog headers
16:9Video, hero imagery, cards. The default.
21:9Full-bleed banners and page headers
3:4 / 9:16Portrait 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.