A <figure>/<figcaption> pairing that wraps self-contained content (an image, illustration, chart, code listing, table) with an optional caption in one of three positions — below (default), above, or overlaid on the media. size scales the caption's type off the body/{size} ramp (the media is size-independent, as in Figma); the caption's own align matches Figma's Figcaption Align axis.
Registry-only
No headless primitive — this component ships only as a copied styled file, so there is no Headless mode. primitiv add figure installs it whichever mode you are reading.
Density is set by a data-density ancestor — the Context system, not a Figure prop.
Installation
npx primitiv add figure
pnpm dlx primitiv add figure
yarn dlx primitiv add figure
bunx primitiv add figure
Import
import{Figure}from"@/components/ui/figure";
No headless primitive — this one ships only as a copied styled surface, so primitiv add is the only way in, whichever mode you are reading.
Anatomy
Three parts: Figure renders the <figure> and owns captionPosition/size; Figure.Media wraps the embedded content; Figure.Caption renders the <figcaption> (with its own align).
<Figure><Figure.Media/><Figure.Caption/></Figure>
Props
Generated from the copied file’s props type and contract.json. Never hand-maintained.
Figure
Extends HTMLElement — every native attribute of that element is accepted and forwarded.
Prop
Type
Default
Description
captionPosition
"below" | "above" | "overlay"
below
Caption placement relative to the media.
size
"xs" | "sm" | "md" | "lg" | "xl"
md
Caption type size, matching Figma's Size axis — it drives the nested Figcaption's body/{size} scale only; the media is size-independent. data-density scales each size further.
Figure.Media
Headless only — the copied file renders this part for you and exports no separate component, so there is nothing to import under Styled.
Extends HTMLDivElement — every native attribute of that element is accepted and forwarded.
No props of its own.
Figure.Caption
Headless only — the copied file renders this part for you and exports no separate component, so there is nothing to import under Styled.
Extends HTMLElement — every native attribute of that element is accepted and forwarded.
Prop
Type
Default
Description
align
"start" | "center" | "end"
start
Caption text alignment, matching Figma's Figcaption Align axis. Direction-aware — start/end follow the reading direction, flipping under RTL.
Styling contract
9 CSS custom properties on .primitiv-figure — mode-agnostic. These names are the stable surface; the values are not.
Figure renders a real <figure> and Figure.Caption a real <figcaption>, so assistive tech associates the caption with its content — the caption is not just text that happens to sit nearby.
The media inside still carries its own meaning: an <img> needs its alt, a chart its description. The <figcaption> names the figure, it does not replace the media's own alternative text.
The overlay caption sits over the media with a scrim for contrast, but the association is structural (figure/figcaption), so it survives regardless of where the caption is placed.
Examples
Every example below reacts to the density control. Currently showing Styled mode.
Caption position
captionPosition places the <figcaption>: below (the default) and above sit it outside the media, while overlay floats it over the bottom of the media with a scrim — the hero-image treatment. Figure.Caption takes an align for left/centre/right within the row.
16 : 9 media
captionPosition="below"
16 : 9 media
captionPosition="overlay"
import{Figure}from"@/components/ui/figure";<FigurecaptionPosition="overlay"><Figure.Media><imgsrc="/cover.jpg"alt=""/></Figure.Media><Figure.Captionalign="center">On the cover</Figure.Caption></Figure>