Shadcn Anatomy for React and Tailwind
Labeled part diagrams for component docs.
Installation
bunx --bun shadcn@latest add https://kit.dev/r/anatomy.jsonpnpm dlx shadcn@latest add https://kit.dev/r/anatomy.jsonnpx shadcn@latest add https://kit.dev/r/anatomy.jsonyarn shadcn@latest add https://kit.dev/r/anatomy.jsonInstall the following dependencies:
bun add @ark-ui/reactpnpm add @ark-ui/reactnpm install @ark-ui/reactyarn add @ark-ui/reactCopy and paste the following code into your project.
"use client";
import { useLocaleContext } from "@ark-ui/react/locale";
import type React from "react";
import { createContext, useContext, useId } from "react";
import { cn } from "@/lib/utils";
export const ANATOMY_HEIGHT = 630;
export const ANATOMY_WIDTH = 1200;
export type AnatomyAlign = "start" | "center" | "end";
export type AnatomyCallout = "line" | "brace" | "elbow";
export type AnatomyDir = "ltr" | "rtl";
export type AnatomySide = "top" | "bottom" | "start" | "end";
export type AnatomyTone = "surface" | "emphasis";
export interface AnatomyColors {
bg: string;
emphasis: string;
fg: string;
line: string;
outline: string;
surface: string;
}
export interface AnatomyCorners {
bl?: number;
br?: number;
tl?: number;
tr?: number;
}
export interface AnatomyRect {
height: number;
width: number;
x: number;
y: number;
}
type PhysicalSide = "top" | "right" | "bottom" | "left";
const BOX_OUTSET = 32;
const BOX_RADIUS = 16;
const BRACE_GAP = 24;
const BRACE_TICK = 12;
const LINE_LENGTH = 26;
const LINE_CAP = 18;
const ELBOW_ARM = 110;
const ELBOW_STEM = 26;
const LABEL_GAP = 16;
const LABEL_SIZE = 22;
const STROKE = 2.5;
const SHARE_FONT =
'"Helvetica Neue", Helvetica, Arial, "DejaVu Sans", "Liberation Sans", sans-serif';
const DOCS_FONT = "var(--font-sans), ui-sans-serif, system-ui, sans-serif";
const SHARE_SPACE_EM = 0.55;
const shareAdvance = (text: string, fontSize: number) => {
let em = 0;
for (const char of text) {
if ("ilI.,'".includes(char)) {
em += 0.3;
} else if ("mwMW".includes(char)) {
em += 0.92;
} else if (char !== char.toLowerCase()) {
em += 0.76;
} else {
em += 0.58;
}
}
return em * fontSize;
};
const BRAND_CHAR_EM = 0.56;
const BRAND_DEFAULT = "shadcn.io";
const BRAND_FONT = 18;
const BRAND_GAP = 10;
const BRAND_MARK = 26;
const BRAND_MARK_VB = 324;
const BRAND_PAD = 48;
export const anatomyCssColors: AnatomyColors = {
bg: "var(--anatomy-bg)",
emphasis: "var(--anatomy-emphasis)",
fg: "var(--anatomy-fg)",
line: "var(--anatomy-line)",
outline: "var(--anatomy-outline)",
surface: "var(--anatomy-surface)",
};
export const anatomyShareColors: AnatomyColors = {
bg: "#5a5a5a",
emphasis: "#171717",
fg: "#f5f5f5",
line: "#f0f0f0",
outline: "#e8e8e8",
surface: "#2f2f2f",
};
const anatomyVars = {
"--anatomy-bg": "oklch(from var(--primary) 0.42 c h)",
"--anatomy-emphasis": "oklch(from var(--primary) 0.24 c h)",
"--anatomy-fg": "oklch(from var(--primary) 0.97 calc(c * 0.2) h)",
"--anatomy-line": "oklch(from var(--primary) 0.93 c h)",
"--anatomy-outline": "oklch(from var(--primary) 0.9 c h / 0.72)",
"--anatomy-surface": "oklch(from var(--primary) 0.33 c h)",
} as React.CSSProperties;
interface AnatomyContextValue {
colors: AnatomyColors;
dir: AnatomyDir;
frame: AnatomyRect | null;
height: number;
origin: { x: number; y: number };
share: boolean;
width: number;
}
const AnatomyContext = createContext<AnatomyContextValue | null>(null);
export const useAnatomyContext = () => {
const context = useContext(AnatomyContext);
if (!context) {
throw new Error("AnatomyPart must be used within Anatomy");
}
return context;
};
export const useAnatomyPart = useAnatomyContext;
const expand = (rect: AnatomyRect, outset: number): AnatomyRect => ({
height: rect.height + outset * 2,
width: rect.width + outset * 2,
x: rect.x - outset,
y: rect.y - outset,
});
const flipX = (
x: number,
width: number,
parentWidth: number,
dir: AnatomyDir
) => (dir === "rtl" ? parentWidth - x - width : x);
const physicalSide = (side: AnatomySide, dir: AnatomyDir): PhysicalSide => {
if (side === "top" || side === "bottom") {
return side;
}
if (side === "start") {
return dir === "rtl" ? "right" : "left";
}
return dir === "rtl" ? "left" : "right";
};
const alongX = (rect: AnatomyRect, align: AnatomyAlign, dir: AnatomyDir) => {
let t = 0.5;
if (align === "start") {
t = 0.28;
} else if (align === "end") {
t = 0.72;
}
if (dir === "rtl" && align !== "center") {
t = 1 - t;
}
return rect.x + rect.width * t;
};
const alongY = (rect: AnatomyRect, align: AnatomyAlign) => {
if (align === "start") {
return rect.y + rect.height * 0.28;
}
if (align === "end") {
return rect.y + rect.height * 0.72;
}
return rect.y + rect.height * 0.5;
};
const flipCorners = (
corners: AnatomyCorners,
dir: AnatomyDir
): AnatomyCorners => {
if (dir === "ltr") {
return corners;
}
return {
bl: corners.br,
br: corners.bl,
tl: corners.tr,
tr: corners.tl,
};
};
const roundedRectPath = (rect: AnatomyRect, corners: AnatomyCorners) => {
const { x, y, width: w, height: h } = rect;
const tl = Math.min(corners.tl ?? 0, w / 2, h / 2);
const tr = Math.min(corners.tr ?? 0, w / 2, h / 2);
const br = Math.min(corners.br ?? 0, w / 2, h / 2);
const bl = Math.min(corners.bl ?? 0, w / 2, h / 2);
const topRight = tr
? `A ${tr} ${tr} 0 0 1 ${x + w} ${y + tr}`
: `L ${x + w} ${y}`;
const bottomRight = br
? `A ${br} ${br} 0 0 1 ${x + w - br} ${y + h}`
: `L ${x + w} ${y + h}`;
const bottomLeft = bl
? `A ${bl} ${bl} 0 0 1 ${x} ${y + h - bl}`
: `L ${x} ${y + h}`;
const topLeft = tl ? `A ${tl} ${tl} 0 0 1 ${x + tl} ${y}` : `L ${x} ${y}`;
return `M ${x + tl} ${y} H ${x + w - tr} ${topRight} V ${y + h - br} ${bottomRight} H ${x + bl} ${bottomLeft} V ${y + tl} ${topLeft} Z`;
};
const resolveLabel = (
label: string | false | undefined,
name: string | undefined
) => {
if (label === false) {
return null;
}
if (label !== undefined) {
return label === "" ? null : label;
}
return name ?? null;
};
const resolveBrand = (brand: boolean | string | undefined) => {
if (brand === true) {
return BRAND_DEFAULT;
}
if (typeof brand === "string") {
const text = brand.trim();
return text === "" ? null : text;
}
return null;
};
const resolveCorners = (
rx: number | AnatomyCorners | undefined,
dir: AnatomyDir
): AnatomyCorners | null => {
if (rx === undefined) {
return null;
}
if (typeof rx === "number") {
return { bl: rx, br: rx, tl: rx, tr: rx };
}
return flipCorners(rx, dir);
};
const fontFamily = (share: boolean) => (share ? SHARE_FONT : DOCS_FONT);
interface SvgLabelProps {
anchor: "start" | "middle" | "end";
colors: AnatomyColors;
share: boolean;
text: string;
x: number;
y: number;
}
const PlacedText = (props: {
anchor: "start" | "middle" | "end";
fill: string;
fontSize: number;
fontWeight: number;
share: boolean;
slot?: string;
text: string;
x: number;
y: number;
}) => {
const family = fontFamily(props.share);
if (!(props.share && props.text.includes(" "))) {
return (
<text
data-slot={props.slot}
dominantBaseline="middle"
fill={props.fill}
fontFamily={family}
fontSize={props.fontSize}
fontWeight={props.fontWeight}
letterSpacing={props.share ? undefined : "0.02em"}
textAnchor={props.anchor}
textRendering="geometricPrecision"
x={props.x}
y={props.y}
>
{props.text}
</text>
);
}
const words = props.text.split(" ");
const gap = props.fontSize * SHARE_SPACE_EM;
const widths = words.map((word) => shareAdvance(word, props.fontSize));
const total =
widths.reduce((sum, width) => sum + width, 0) + gap * (words.length - 1);
let cursor = props.x;
if (props.anchor === "middle") {
cursor -= total / 2;
} else if (props.anchor === "end") {
cursor -= total;
}
return (
<g data-slot={props.slot}>
{words.map((word, index) => {
const x = cursor;
cursor += widths[index] + gap;
return (
<text
dominantBaseline="middle"
fill={props.fill}
fontFamily={family}
fontSize={props.fontSize}
fontWeight={props.fontWeight}
key={`${index}-${word}`}
textAnchor="start"
textRendering="geometricPrecision"
x={x}
y={props.y}
>
{word}
</text>
);
})}
</g>
);
};
const SvgLabel = (props: SvgLabelProps) => (
<PlacedText
anchor={props.anchor}
fill={props.colors.line}
fontSize={LABEL_SIZE}
fontWeight={500}
share={props.share}
slot="anatomy-label"
text={props.text}
x={props.x}
y={props.y}
/>
);
const AnatomyBrand = (props: {
colors: AnatomyColors;
share: boolean;
text: string;
}) => {
const { colors, share, text } = props;
const textWidth = text.length * BRAND_FONT * BRAND_CHAR_EM;
const x = ANATOMY_WIDTH - BRAND_PAD - BRAND_MARK - BRAND_GAP - textWidth;
const y = ANATOMY_HEIGHT - BRAND_PAD - BRAND_MARK;
const scale = BRAND_MARK / BRAND_MARK_VB;
return (
<g data-slot="anatomy-brand" opacity={0.92}>
<g transform={`translate(${x} ${y}) scale(${scale})`}>
<rect fill={colors.fg} height="323" rx="161.5" width="323" x="0.5" />
<circle cx="162" cy="161.5" fill={colors.bg} r="60" />
</g>
<PlacedText
anchor="start"
fill={colors.fg}
fontSize={BRAND_FONT}
fontWeight={700}
share={share}
text={text}
x={x + BRAND_MARK + BRAND_GAP}
y={y + BRAND_MARK / 2}
/>
</g>
);
};
const StrokePath = (props: { colors: AnatomyColors; d: string }) => (
<path
d={props.d}
data-slot="anatomy-stroke"
fill="none"
shapeRendering="geometricPrecision"
stroke={props.colors.line}
strokeLinecap="square"
strokeLinejoin="miter"
strokeWidth={STROKE}
/>
);
const wallRect = (frame: AnatomyRect | null, part: AnatomyRect): AnatomyRect =>
frame ? expand(frame, BOX_OUTSET) : part;
const lineGeom = (props: {
align: AnatomyAlign;
dir: AnatomyDir;
part: AnatomyRect;
side: PhysicalSide;
wall: AnatomyRect;
}) => {
const { align, dir, part, side, wall } = props;
const x = alongX(part, align, dir);
const y = alongY(part, align);
const tick = LINE_CAP / 2;
if (side === "top") {
return {
anchor: "middle" as const,
d: `M ${x - tick} ${wall.y} H ${x + tick} M ${x} ${wall.y} V ${wall.y - LINE_LENGTH}`,
x,
y: wall.y - LINE_LENGTH - LABEL_GAP - LABEL_SIZE / 2,
};
}
if (side === "bottom") {
return {
anchor: "middle" as const,
d: `M ${x - tick} ${wall.y + wall.height} H ${x + tick} M ${x} ${wall.y + wall.height} V ${wall.y + wall.height + LINE_LENGTH}`,
x,
y: wall.y + wall.height + LINE_LENGTH + LABEL_GAP + LABEL_SIZE / 2,
};
}
if (side === "left") {
return {
anchor: "end" as const,
d: `M ${wall.x} ${y - tick} V ${y + tick} M ${wall.x} ${y} H ${wall.x - LINE_LENGTH}`,
x: wall.x - LINE_LENGTH - LABEL_GAP,
y,
};
}
return {
anchor: "start" as const,
d: `M ${wall.x + wall.width} ${y - tick} V ${y + tick} M ${wall.x + wall.width} ${y} H ${wall.x + wall.width + LINE_LENGTH}`,
x: wall.x + wall.width + LINE_LENGTH + LABEL_GAP,
y,
};
};
const braceGeom = (props: { part: AnatomyRect; side: PhysicalSide }) => {
const { part, side } = props;
if (side === "left") {
const x = part.x - BRACE_GAP;
return {
anchor: "end" as const,
d: `M ${x} ${part.y} H ${x - BRACE_TICK} V ${part.y + part.height} H ${x}`,
x: x - BRACE_TICK - LABEL_GAP,
y: part.y + part.height / 2,
};
}
if (side === "right") {
const x = part.x + part.width + BRACE_GAP;
return {
anchor: "start" as const,
d: `M ${x} ${part.y} H ${x + BRACE_TICK} V ${part.y + part.height} H ${x}`,
x: x + BRACE_TICK + LABEL_GAP,
y: part.y + part.height / 2,
};
}
if (side === "top") {
const y = part.y - BRACE_GAP;
return {
anchor: "middle" as const,
d: `M ${part.x} ${y} V ${y - BRACE_TICK} H ${part.x + part.width} V ${y}`,
x: part.x + part.width / 2,
y: y - BRACE_TICK - LABEL_GAP - LABEL_SIZE / 2,
};
}
const y = part.y + part.height + BRACE_GAP;
return {
anchor: "middle" as const,
d: `M ${part.x} ${y} V ${y + BRACE_TICK} H ${part.x + part.width} V ${y}`,
x: part.x + part.width / 2,
y: y + BRACE_TICK + LABEL_GAP + LABEL_SIZE / 2,
};
};
const elbowGeom = (props: {
align: AnatomyAlign;
dir: AnatomyDir;
part: AnatomyRect;
side: PhysicalSide;
wall: AnatomyRect;
}) => {
const { align, dir, part, side, wall } = props;
const towardStart = dir === "rtl" ? 1 : -1;
const x = alongX(part, align, dir);
const y = alongY(part, align);
const wallBottom = wall.y + wall.height;
const wallRight = wall.x + wall.width;
if (side === "bottom") {
const armX = x + towardStart * ELBOW_ARM;
return {
anchor: "middle" as const,
d: `M ${x} ${part.y + part.height} L ${x} ${wallBottom} L ${armX} ${wallBottom}`,
x: (x + armX) / 2,
y: wallBottom + LABEL_GAP + LABEL_SIZE / 2,
};
}
if (side === "top") {
const armX = x + towardStart * ELBOW_ARM;
return {
anchor: "middle" as const,
d: `M ${x} ${part.y} L ${x} ${wall.y} L ${armX} ${wall.y}`,
x: (x + armX) / 2,
y: wall.y - LABEL_GAP - LABEL_SIZE / 2,
};
}
if (side === "left") {
const armY = align === "start" ? y - ELBOW_ARM : y + ELBOW_ARM;
const stemX = wall === part ? part.x - ELBOW_STEM : wall.x;
return {
anchor: "middle" as const,
d: `M ${part.x} ${y} L ${stemX} ${y} L ${stemX} ${armY}`,
x: stemX,
y:
align === "start"
? armY - LABEL_GAP - LABEL_SIZE / 2
: armY + LABEL_GAP + LABEL_SIZE / 2,
};
}
const armY = align === "start" ? y - ELBOW_ARM : y + ELBOW_ARM;
const stemX = wall === part ? part.x + part.width + ELBOW_STEM : wallRight;
return {
anchor: "middle" as const,
d: `M ${part.x + part.width} ${y} L ${stemX} ${y} L ${stemX} ${armY}`,
x: stemX,
y:
align === "start"
? armY - LABEL_GAP - LABEL_SIZE / 2
: armY + LABEL_GAP + LABEL_SIZE / 2,
};
};
const Callout = (props: {
align: AnatomyAlign;
callout: AnatomyCallout;
colors: AnatomyColors;
dir: AnatomyDir;
frame: AnatomyRect | null;
label: string;
part: AnatomyRect;
share: boolean;
side: AnatomySide;
}) => {
const { align, callout, colors, dir, frame, label, part, share, side } =
props;
const physical = physicalSide(side, dir);
const wall = callout === "brace" ? part : wallRect(frame, part);
let geom = lineGeom({ align, dir, part, side: physical, wall });
if (callout === "brace") {
geom = braceGeom({ part, side: physical });
} else if (callout === "elbow") {
geom = elbowGeom({ align, dir, part, side: physical, wall });
}
return (
<>
<StrokePath colors={colors} d={geom.d} />
<SvgLabel
anchor={geom.anchor}
colors={colors}
share={share}
text={label}
x={geom.x}
y={geom.y}
/>
</>
);
};
export interface AnatomyProps {
brand?: boolean | string;
children?: React.ReactNode;
className?: string;
colors?: AnatomyColors;
dir?: AnatomyDir;
share?: boolean;
title?: string;
}
export interface AnatomyPartProps {
align?: AnatomyAlign;
box?: boolean;
callout?: AnatomyCallout;
children?: React.ReactNode;
height?: number;
label?: string | false;
name?: string;
rx?: number | AnatomyCorners;
side?: AnatomySide;
tone?: AnatomyTone;
width?: number;
x?: number;
y?: number;
}
export const Anatomy = (props: AnatomyProps) => {
const locale = useLocaleContext();
const {
brand = false,
children,
className,
colors: colorsProp,
dir: dirProp,
share = false,
title = "Component anatomy",
} = props;
const dir = dirProp ?? locale.dir;
const colors = colorsProp ?? (share ? anatomyShareColors : anatomyCssColors);
const brandText = resolveBrand(brand);
const titleId = useId();
const context: AnatomyContextValue = {
colors,
dir,
frame: null,
height: ANATOMY_HEIGHT,
origin: { x: 0, y: 0 },
share,
width: ANATOMY_WIDTH,
};
const scene = (
<>
<rect
fill={colors.bg}
height={ANATOMY_HEIGHT}
rx={24}
width={ANATOMY_WIDTH}
/>
{children}
{brandText ? (
<AnatomyBrand colors={colors} share={share} text={brandText} />
) : null}
</>
);
if (share) {
return (
<AnatomyContext.Provider value={context}>
<svg
aria-labelledby={titleId}
data-slot="anatomy"
direction={dir}
height={ANATOMY_HEIGHT}
role="img"
viewBox={`0 0 ${ANATOMY_WIDTH} ${ANATOMY_HEIGHT}`}
width={ANATOMY_WIDTH}
xmlns="http://www.w3.org/2000/svg"
>
<title id={titleId}>{title}</title>
{scene}
</svg>
</AnatomyContext.Provider>
);
}
return (
<AnatomyContext.Provider value={context}>
<figure
aria-labelledby={titleId}
className={cn("relative w-full min-w-0 select-none", className)}
data-slot="anatomy"
dir={dir}
style={{
...anatomyVars,
aspectRatio: `${ANATOMY_WIDTH} / ${ANATOMY_HEIGHT}`,
}}
>
<svg
aria-label={title}
className="block h-auto w-full"
data-slot="anatomy-svg"
direction={dir}
height={ANATOMY_HEIGHT}
style={anatomyVars}
viewBox={`0 0 ${ANATOMY_WIDTH} ${ANATOMY_HEIGHT}`}
width={ANATOMY_WIDTH}
>
<title id={titleId}>{title}</title>
{scene}
</svg>
</figure>
</AnatomyContext.Provider>
);
};
export const AnatomyPart = (props: AnatomyPartProps) => {
const {
align = "center",
box = false,
callout = "line",
children,
height: heightProp,
label,
name,
rx,
side = "top",
tone,
width: widthProp,
x = 0,
y = 0,
} = props;
const parent = useAnatomyContext();
const width = widthProp ?? parent.width - x;
const height = heightProp ?? parent.height - y;
const displayX = parent.origin.x + flipX(x, width, parent.width, parent.dir);
const displayY = parent.origin.y + y;
const partRect: AnatomyRect = {
height,
width,
x: displayX,
y: displayY,
};
const corners = resolveCorners(rx, parent.dir);
let fill: string | null = null;
if (tone === "emphasis") {
fill = parent.colors.emphasis;
} else if (tone === "surface") {
fill = parent.colors.surface;
}
const resolvedLabel = resolveLabel(label, name);
const frame = box ? partRect : parent.frame;
const childContext: AnatomyContextValue = {
colors: parent.colors,
dir: parent.dir,
frame,
height,
origin: { x: displayX, y: displayY },
share: parent.share,
width,
};
return (
<AnatomyContext.Provider value={childContext}>
<g
data-align={align}
data-callout={callout}
data-name={name}
data-side={side}
data-slot="anatomy-part"
data-tone={tone}
>
{fill && corners ? (
<path d={roundedRectPath(partRect, corners)} fill={fill} />
) : null}
{fill && !corners ? (
<rect
fill={fill}
height={height}
width={width}
x={displayX}
y={displayY}
/>
) : null}
{children}
{box ? (
<rect
data-slot="anatomy-box"
fill="none"
height={height + BOX_OUTSET * 2}
rx={BOX_RADIUS}
shapeRendering="geometricPrecision"
stroke={parent.colors.outline}
strokeDasharray="7 6"
strokeWidth={STROKE}
width={width + BOX_OUTSET * 2}
x={displayX - BOX_OUTSET}
y={displayY - BOX_OUTSET}
/>
) : null}
{resolvedLabel ? (
<Callout
align={align}
callout={callout}
colors={parent.colors}
dir={parent.dir}
frame={frame}
label={resolvedLabel}
part={partRect}
share={parent.share}
side={side}
/>
) : null}
</g>
</AnatomyContext.Provider>
);
};
export interface AnatomyTextProps {
children: string;
fontSize?: number;
fontWeight?: number;
padX?: number;
}
export const AnatomyText = (props: AnatomyTextProps) => {
const { children, fontSize = 26, fontWeight = 500, padX = 24 } = props;
const { colors, dir, height, origin, share, width } = useAnatomyPart();
const x = dir === "rtl" ? origin.x + width - padX : origin.x + padX;
const anchor = dir === "rtl" ? "end" : "start";
return (
<PlacedText
anchor={anchor}
fill={colors.fg}
fontSize={fontSize}
fontWeight={fontWeight}
share={share}
text={children}
x={x}
y={origin.y + height / 2}
/>
);
};
export interface AnatomyChevronProps {
align?: "center" | "end";
direction: "down" | "up";
size?: number;
}
export const AnatomyChevron = (props: AnatomyChevronProps) => {
const { align = "end", direction, size = 18 } = props;
const { colors, dir, height, origin, width } = useAnatomyPart();
const pad = 22;
let cx = origin.x + width / 2;
if (align === "end") {
cx =
dir === "rtl"
? origin.x + pad + size / 2
: origin.x + width - pad - size / 2;
}
const cy = origin.y + height / 2;
const w = size;
const h = size * 0.55;
const down = direction === "down";
const y1 = down ? cy - h / 3 : cy + h / 3;
const y2 = down ? cy + h / 3 : cy - h / 3;
return (
<path
d={`M ${cx - w / 2} ${y1} L ${cx} ${y2} L ${cx + w / 2} ${y1}`}
fill="none"
stroke={colors.fg}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2.4}
/>
);
};
export const AnatomyCheck = (props: { size?: number }) => {
const { size = 18 } = props;
const { colors, height, origin, width } = useAnatomyPart();
const cx = origin.x + width / 2;
const cy = origin.y + height / 2;
const s = size / 2;
return (
<path
d={`M ${cx - s * 0.55} ${cy} L ${cx - s * 0.1} ${cy + s * 0.45} L ${cx + s * 0.6} ${cy - s * 0.4}`}
fill="none"
stroke={colors.fg}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2.6}
/>
);
};
export const AnatomyX = (props: { size?: number }) => {
const { size = 16 } = props;
const { colors, height, origin, width } = useAnatomyPart();
const cx = origin.x + width / 2;
const cy = origin.y + height / 2;
const s = size / 2;
return (
<path
d={`M ${cx - s} ${cy - s} L ${cx + s} ${cy + s} M ${cx + s} ${cy - s} L ${cx - s} ${cy + s}`}
fill="none"
stroke={colors.fg}
strokeLinecap="round"
strokeWidth={2.6}
/>
);
};
export { AnatomyContext };Update the import paths to match your project setup.
Anatomy
Anatomy
├── AnatomyPart
├── AnatomyText
├── AnatomyChevron
└── AnatomyCheckshadcn.io Anatomy is an SVG scene: parts are rectangles in a 1200×630 viewBox, callouts are strokes, and labels are real <text>. Pass dir and a labels map so the same diagram can flip for RTL and later locales. Inner line / elbow callouts snap to the nearest box frame. Pass brand for a shadcn.io lockup in the bottom-right.
The stage is a mid-tone mix of --primary (--anatomy-bg) so Neutral light does not collapse to black, and mock fills / leaders stay on theme.
Usage
import { Anatomy, AnatomyPart } from "@/components/ui/anatomy";<Anatomy brand title="Checkbox anatomy">
<AnatomyPart box name="root" x={360} y={267} width={480} height={96}>
<AnatomyPart name="control" side="start" tone="emphasis">
…
</AnatomyPart>
<AnatomyPart name="label" side="bottom">
Checkbox Label
</AnatomyPart>
</AnatomyPart>
</Anatomy>name is the default label. Pass label to override, or label={false} to mark a region with no callout.
Coordinates are LTR user space relative to the parent part. dir="rtl" mirrors x inside each parent and maps start / end. Text glyphs are not flipped. brand stays in the physical bottom-right (it is a signature, not a callout).
Examples
Accordion
callout="brace" spans a side (the item group). callout="elbow" is an L along the dashed frame — use it for a corner part like item indicator. align shifts a leader along that side so two top labels do not collide. Pass a labels object to translate callouts and mock copy.
Action Bar
A horizontal toolbar: content is the dashed frame, body is a brace over the actions, and separator uses callout="elbow" like Accordion’s item indicator.
Table of Contents
A document plus sticky nav: content is the article, nav holds title and link rows, and indicator uses callout="elbow" on the active row.
Guides
Anatomy vs Hint
| Anatomy | Hint | |
|---|---|---|
| Shape | SVG stage + callouts | Hover/focus tooltip |
| Use when | Component part maps in docs | A short hint on a control |
Use Hint for interactive help. Use Anatomy when the labels are the diagram.
Lightbox
On a component page (for example Accordion or Action Bar, above the live preview), wrap the figure in ImageZoom. Leave the examples on this page unwrapped so the scene stays inspectable. The zoomed layer is a live SVG clone — RTL and --primary follow the page.
Callouts
callout="line"(default) — a short leader from the dashed frame (or the part edge) to the label, with a T-tick at the attachment.callout="elbow"— an L: from the part to the frame, then along the frame towardstart.callout="brace"— a bracket alongside, for a group of rows or a column.box— dashed rounded rect around the part (usually the root). Inner callouts snap to this frame.side/align—topbottomstartend, andstartcenterendalong that side.
Direction and labels
dir defaults to the Ark locale (ltr unless a LocaleProvider is set). start is the inline-start edge: left in LTR, right in RTL. Elbows on side="bottom" drop from the part, then run toward start along the dashed frame.
Put every visible string in a labels map so a locale file can replace them later.
Mock fills
tone="surface" and tone="emphasis" paint with --anatomy-surface and --anatomy-emphasis. They only look right on the Anatomy stage. AnatomyText, AnatomyChevron, AnatomyCheck, and AnatomyX draw mock copy and icons in the current part.
API Reference
shadcn.io Anatomy draws in SVG. Labels are real text. Defaults below are shadcn.io values.
Anatomy
Root. Renders a figure wrapping an SVG, or a standalone SVG when share is set.
| Attribute | Description |
|---|---|
data-slot | anatomy |
dir | ltr or rtl |
The stage is the SVG (1200×630, h-auto w-full) with a rounded fill. Labels are data-slot="anatomy-label". Tokens: --anatomy-bg, --anatomy-surface, --anatomy-emphasis, --anatomy-line, --anatomy-outline, --anatomy-fg.
AnatomyPart
One annotated rectangle in the scene. Coordinates are relative to the parent part.
| Attribute | Description |
|---|---|
data-slot | anatomy-part |
data-name | name |
data-side | side |
data-align | align |
data-callout | callout |
data-tone | tone when set |
Accessibility
The stage is a figure with aria-label from title. Callout labels are visible SVG text (indexable). Do not put interactive controls in the diagram unless the same information is available as text next to it.
Keyboard support
The diagram is not interactive. Focus stays in the page.