Row
Apply the row patch to any block element to lay out its children as a horizontal flex row with spacing between them, vertically centered by default — the general-purpose primitive for icon+label rows, field rows, and button groups, instead of hand-rolling display: flex; alignItems: center; gap: .... toolbar is a semantic alias of this same shape for headers/nav bars (forwards gap/wrap/justify/align/density).
Props
| Prop | Type | Default | Description |
|---|---|---|---|
gap | number | 4 | Spacing multiplier for gap between items. With density: true (default): themeSpacing(density × gap) — at default density (1.5), gap 4 = 1.5em. With density: false: bare themeSpacing(gap) — gap 4 = 1em. |
align | "flex-start" | "center" | "flex-end" | "stretch" | "baseline" | "center" | Cross-axis alignment (alignItems). |
justify | "flex-start" | "center" | "flex-end" | "space-between" | "space-around" | "space-evenly" | unset | Main-axis distribution (justifyContent). Left unset by default (flex's own default, flex-start). |
wrap | boolean | false | Allow items to wrap onto multiple lines (flexWrap: "wrap"). |
density | boolean | true | When true, gap is multiplied by theme density (bounded-control mode). When false, gap is structural themeSpacing(n) with no density multiply — page/form columns. |
Example
import { row } from "@domphy/ui";
const Field = {
div: [{ label: "Name" }, { input: null, placeholder: "Jane" }],
$: [row({ justify: "space-between" })],
};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";
import type { FlexAlign, FlexJustify } from "./stack.js";
/**
* A horizontal flex row with spacing between children, vertically centered by
* default. The general-purpose primitive for icon+label rows, field rows,
* and button groups — instead of hand-rolling `display: flex; alignItems:
* center; gap: ...`. `toolbar()` is a semantic alias of this same shape for
* headers/nav bars. Styles the host only; apply to any block element.
*
* @param props.gap - Spacing multiplier for gap between items (default 4).
* Bounded-control mode (`density: true`, default): `themeSpacing(themeDensity(l) * gap)`
* — at default density 1.5, gap 4 = 1.5em.
* Structural/page-column mode (`density: false`): bare `themeSpacing(gap)` with no
* density multiply — gap 4 = 1em, matching AGENTS.md "bare themeSpacing(n)".
* @param props.align - Cross-axis alignment (`alignItems`). Defaults to `"center"`.
* @param props.justify - Main-axis distribution (`justifyContent`). Unset by default (flex-start).
* @param props.wrap - Allow items to wrap onto multiple lines. Defaults to `false`.
* @param props.density - When true (default), gap is multiplied by theme density
* (bounded controls). When false, gap is structural `themeSpacing(n)` — use this
* for page/form columns.
* @example { div: [{ span: "Icon" }, { span: "Label" }], $: [row()] }
* @example { div: [...], $: [row({ justify: "space-between", wrap: true })] }
* @example { div: [...], $: [row({ gap: 4, density: false })] }
*/
function row(
props: {
gap?: number;
align?: FlexAlign;
justify?: FlexJustify;
wrap?: boolean;
density?: boolean;
} = {},
): PartialElement {
const {
gap = 4,
align = "center",
justify,
wrap = false,
density = true,
} = props;
return {
style: {
display: "flex",
alignItems: align,
gap: density
? (listener) => themeSpacing(themeDensity(listener) * gap)
: () => themeSpacing(gap),
...(justify ? { justifyContent: justify } : {}),
...(wrap ? { flexWrap: "wrap" } : {}),
},
};
}
export { row };
export type { FlexJustify };