Figure

Pairs media with a caption. Use it in editorial and article layouts where the image carries information the surrounding prose does not.

Content Figure.jsx Figure.d.ts Figure.md

Playground

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

Install and import

Figure is in @dovetail-ds/react. Install it with React, if you haven't:

npm install @dovetail-ds/react react react-dom
import { Figure } 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

Pairs media with a caption. Use it in editorial and article layouts where the image carries information the surrounding prose does not.

Caption is not alt text

They serve different readers and must say different things. alt describes what the image shows, for someone who cannot see it. The caption adds context everyone needs: a date, a place, what to notice. Repeating one in the other wastes a screen reader's time.

``jsx <Figure caption="Throughput held steady through the March migration." credit="Photo: Internal"> <Image src={chart} alt="Line chart of requests per second across three months" ratio="4:3" /> </Figure> ``

Credit

credit renders smaller and in the mono role, below the caption. Use it for attribution and licence text only. If the source matters to the argument, put it in the caption instead.

Props

The typed contract, from Figure.d.ts.

import * as React from "react";

/** Wraps media with a caption and an optional credit line. */
export interface FigureProps extends React.HTMLAttributes<HTMLElement> {
  /** Describes or extends the image. Not a repeat of the alt text. */
  caption?: React.ReactNode;
  /** Photographer, source, or licence line. */
  credit?: React.ReactNode;
  /** @default "start" */
  align?: "start" | "center";
  /** The media element, usually an \`Image\`. */
  children?: React.ReactNode;
}

export declare function Figure(props: FigureProps): React.JSX.Element;

Tokens it reads

Every custom property Figure.jsx resolves, read from the source: 4 semantic, 6 primitive. Component tier first, because that is the one to reach for.

  • semantic Shared roles. Override one and everything using that role moves with it.
  • primitive Raw values. A component reading one directly is a deliberate exception.
TokenTierDeclared as
--dt-space-stack-2xssemanticvar(--dt-dim-1)
--dt-space-stack-smsemanticvar(--dt-dim-3)
--dt-text-secondarysemanticvar(--dt-color-neutral-600)
--dt-text-tertiarysemanticvar(--dt-color-neutral-500)
--dt-font-family-monoprimitive"Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace
--dt-font-family-sansprimitive"Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif
--dt-font-size-smprimitive14px
--dt-font-size-xsprimitive12px
--dt-line-height-smprimitive20px
--dt-line-height-xsprimitive16px

Source

React only, no dependencies, every value a token.

import React from "react";

export function Figure({ caption, credit, align = "start", children, style, ...rest }) {
  return (
    <figure style={{ margin: 0, display: "flex", flexDirection: "column", gap: "var(--dt-space-stack-sm)", ...style }} {...rest}>
      {children}
      {(caption || credit) && (
        <figcaption style={{ display: "flex", flexDirection: "column", gap: "var(--dt-space-stack-2xs)", textAlign: align === "center" ? "center" : "left", alignItems: align === "center" ? "center" : "flex-start" }}>
          {caption && <span style={{ fontFamily: "var(--dt-font-family-sans)", fontSize: "var(--dt-font-size-sm)", lineHeight: "var(--dt-line-height-sm)", color: "var(--dt-text-secondary)" }}>{caption}</span>}
          {credit && <span style={{ fontFamily: "var(--dt-font-family-mono)", fontSize: "var(--dt-font-size-xs)", lineHeight: "var(--dt-line-height-xs)", color: "var(--dt-text-tertiary)" }}>{credit}</span>}
        </figcaption>
      )}
    </figure>
  );
}

In context

Figure also appears on the content reference cards, alongside the rest of its family.