Breadcrumb
Use breadcrumb on a nav element. It styles direct children automatically. Mark the current page by setting ariaCurrent: "page" directly on the current item. The separator is configured once via the separator prop.
Use breadcrumbEllipsis on a button element to represent collapsed breadcrumb items.
Customization
Must see the source of patch at the bottom of each patch page to understand the structure then code it still code as html native element.
There are four levels of customization, in increasing order of effort:
- Patch props. Each patch exposes a small, stable set of props—typically fewer than five. Lowest friction.
- Context attributes. Use
dataTone,dataSize, anddataDensityon a container to shift tone, size, or density for an entire subtree without touching individual elements. - Inline override. Native-wins merge strategy: any property set directly on the element overrides the patch value.
- Create a variant. Clone a similar patch and edit it. Use this only when you need a reusable custom version.
Formulas
Unit - U = fontSize / 4 - convert final values with themeSpacing(n).
Size - n = intrinsic text lines, w = wrapping level, d = density factor:
height = (n * 6 + 2 * d * w) * U
paddingBlock = d * w * U
paddingInline = ceil(3 / w) * d * w * U
radius = d * w * U
Base density d = 1.5:
| U | w=0 | w=1 | w=2 | w=3 |
|---|---|---|---|---|
height (n = 1) | 6 | 9 | 12 | 15 |
| paddingBlock | 0 | 1.5 | 3 | 4.5 |
| paddingInline | 3 | 4.5 | 6 | 4.5 |
| radius | 0 | 1.5 | 3 | 4.5 |
Tone - K = N / 2 where N is the palette length. For N = 18, K = 9.
| Role | Shift | n=0 |
|---|---|---|
| Background | parent +/- n | 0 |
| Text | bg + K | 6 |
| Border | bg + K/2 | 3 |
| Hover | bg + 2K/3 | 4 |
| Selected / Focus | above +/- K/3 | 2-4 |
State shift range: K/3 <= delta <= 2K/3.
import type { PartialElement } from "@domphy/core";
import { toState, type ValueOrState } from "@domphy/core";
import {
type ThemeColor,
themeColor,
themeSize,
themeSpacing,
} from "@domphy/theme";
/**
* A horizontal breadcrumb navigation that lays out its children with a
* separator between items and highlights the `[aria-current=page]` item.
* Apply to a `<nav>` element.
*
* @hostTag nav
* @param props.color - Color tone for links/separators. Optional `ValueOrState<ThemeColor>`, default "neutral".
* @param props.separator - String inserted between items via `::after`. Optional `string`, default "/".
* @example { nav: null, $: [breadcrumb({ separator: "›" })] }
*/
function breadcrumb(
props: { color?: ValueOrState<ThemeColor>; separator?: string } = {},
): PartialElement {
const { separator = "/" } = props;
const color = toState(props.color ?? "neutral", "color");
return {
_onInsert: (node) => {
if (node.tagName !== "nav")
console.warn('"breadcrumb" patch must use nav tag');
},
ariaLabel: "breadcrumb",
style: {
display: "flex",
alignItems: "center",
flexWrap: "wrap",
fontSize: (listener) => themeSize(listener, "inherit"),
gap: themeSpacing(1),
color: (listener) => themeColor(listener, "shift-9", color.get(listener)),
backgroundColor: (listener) =>
themeColor(listener, "inherit", color.get(listener)),
"& > *": {
display: "inline-flex",
alignItems: "center",
color: (listener) =>
themeColor(listener, "shift-8", color.get(listener)),
},
"& > *:not(:last-child)::after": {
content: `"${separator}"`,
color: (listener) =>
themeColor(listener, "shift-4", color.get(listener)),
paddingInlineStart: themeSpacing(1),
},
"& > [aria-current=page]": {
color: (listener) =>
themeColor(listener, "shift-10", color.get(listener)),
pointerEvents: "none",
},
},
};
}
export { breadcrumb };
import type { PartialElement } from "@domphy/core";
import { toState, type ValueOrState } from "@domphy/core";
import {
type ThemeColor,
themeColor,
themeSize,
themeSpacing,
} from "@domphy/theme";
/**
* An ellipsis trigger button for collapsed breadcrumb items, with hover and
* focus-visible states. Apply to a `<button>` element.
*
* @hostTag button
* @param props.color - Color tone for the trigger. Optional `ValueOrState<ThemeColor>`, default "neutral".
* @example { button: "…", $: [breadcrumbEllipsis({ color: "neutral" })] }
*/
function breadcrumbEllipsis(
props: { color?: ValueOrState<ThemeColor> } = {},
): PartialElement {
const color = toState(props.color ?? "neutral", "color");
return {
_onInsert: (node) => {
if (node.tagName !== "button") {
console.warn('"breadcrumbEllipsis" patch must use button tag');
}
},
ariaLabel: "More breadcrumb items",
style: {
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
fontSize: (listener) => themeSize(listener, "inherit"),
paddingInline: themeSpacing(1),
border: "none",
background: "none",
cursor: "pointer",
color: (listener) => themeColor(listener, "shift-8", color.get(listener)),
borderRadius: themeSpacing(1),
"&:hover": {
color: (listener) =>
themeColor(listener, "shift-10", color.get(listener)),
backgroundColor: (listener) =>
themeColor(listener, "shift-2", color.get(listener)),
},
"&:focus-visible": {
outline: (listener) =>
`${themeSpacing(0.5)} solid ${themeColor(listener, "shift-6", color.get(listener))}`,
outlineOffset: themeSpacing(0.5),
},
},
};
}
export { breadcrumbEllipsis };