Stack
Apply the stack patch to any block element to lay out its children as a vertical flex column with spacing between them — the general-purpose primitive for form sections, panel content, and card bodies, instead of hand-rolling display: flex; flexDirection: column; gap: .... Styles the host only; pair it with panelSection for padding or row for a nested horizontal group.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
gap | number | 3 | Spacing multiplier for gap between children. Final gap = themeSpacing(density × gap); at default density (1.5), gap 3 ≈ 1.125em. |
align | "flex-start" | "center" | "flex-end" | "stretch" | "baseline" | unset | Cross-axis alignment (alignItems). Left unset by default (flex's own default, stretch). |
Example
import { stack } from "@domphy/ui";
const Panel = {
div: [{ h3: "Title" }, { p: "Body" }, { button: "Action" }],
$: [stack({ gap: 2 })],
};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 * UBase 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 { themeDensity, themeSpacing } from "@domphy/theme";
type FlexAlign = "flex-start" | "center" | "flex-end" | "stretch" | "baseline";
/**
* A vertical flex column with spacing between children. The general-purpose
* primitive for stacking blocks — form sections, panel content, card bodies —
* instead of hand-rolling `display: flex; flexDirection: column; gap: ...`.
* Styles the host only; apply to any block element.
*
* @param props.gap - Spacing multiplier for gap between children (default 3 = 0.75em at density 1).
* @param props.align - Cross-axis alignment (`alignItems`). Unset by default (flex default, stretch).
* @example { div: [{ h3: "Title" }, { p: "Body" }], $: [stack()] }
* @example { div: [...], $: [stack({ gap: 2, align: "center" })] }
*/
function stack(props: { gap?: number; align?: FlexAlign } = {}): PartialElement {
const { gap = 3, align } = props;
return {
style: {
display: "flex",
flexDirection: "column",
gap: (listener) => themeSpacing(themeDensity(listener) * gap),
...(align ? { alignItems: align } : {}),
},
};
}
export { stack };
export type { FlexAlign };