Skip to main content

x-row

A zero-config flex-row custom element. Children are arranged horizontally. Use gap, align, wrap, and padding attributes — no CSS required.

Quick start
import { registerLayoutElements } from "mates-ui";
registerLayoutElements(); // registers <x-row>, <x-col>, <x-box>

html`
  <x-row align="between center" gap="var(--md-space-3)">
    ${Text("Title", { type: "title-sm" })}
    ${Button("Action", { variant: "filled", size: "sm" })}
  </x-row>
`

App toolbar

import { html, renderApp } from 'mates';
import { Avatar, Button, IconButton, Text } from 'mates-ui';
import { AddIcon, FilterListIcon, NotificationsIcon } from 'mates-icons';

const App = () => () => html`
<x-col gap="var(--md-space-3)">
<x-row align="between center" px="var(--md-space-4)" py="var(--md-space-3)"
style="background:var(--md-surface-container);border-radius:8px;">
<x-row gap="var(--md-space-2)">
${Text("Acme", { type: "title-sm" })}
</x-row>
<x-row gap="var(--md-space-1)">
${IconButton({ icon: NotificationsIcon(), label: "Notifications", variant: "standard", size: "sm" })}
${Avatar({ name: "Jordan Lee", size: "sm" })}
</x-row>
</x-row>
<x-row align="between center" px="var(--md-space-4)" py="var(--md-space-3)"
style="background:var(--md-surface-container);border-radius:8px;">
${Text("Projects", { type: "headline-sm" })}
<x-row gap="var(--md-space-2)">
${Button("Filter", { variant: "ghost", size: "sm", icon: FilterListIcon({ size: 14 }) })}
${Button("New project", { variant: "filled", size: "sm", icon: AddIcon({ size: 14 }) })}
</x-row>
</x-row>
</x-col>
`;

renderApp(App, document.getElementById('app'));

Metric stat bar

import { html, renderApp } from 'mates';
import { Card, Statistic } from 'mates-ui';

const App = () => () => html`
${Card({
variant: "outlined",
body: html`
<x-row gap="var(--md-space-6)" wrap>
${Statistic("$48,200", { title: "Revenue", trend: "up", trendValue: "+12%" })}
${Statistic("3,841", { title: "Users", trend: "up", trendValue: "+8%" })}
${Statistic("2.4%", { title: "Churn", trend: "down", trendValue: "-0.3%" })}
${Statistic("$124", { title: "Avg. LTV", trend: "up", trendValue: "+5%" })}
</x-row>
`,
})}
`;

renderApp(App, document.getElementById('app'));

Filterable tag strip

import { html, atom, renderApp } from 'mates';
import { Card, Chip } from 'mates-ui';

const TAGS = ["All","Design","Frontend","Backend","DevOps","QA","Product","Mobile"];

const App = () => {
const selected = atom([]);
return () => html`
${Card({
variant: "outlined",
body: html`
<x-row gap="var(--md-space-2)" wrap>
${TAGS.map(tag => {
const active = tag === "All" ? selected().length === 0 : selected().includes(tag);
return Chip(tag, {
variant: "filter",
selected: active,
onClick: () => {
if (tag === "All") { selected.set([]); return; }
const cur = selected();
selected.set(cur.includes(tag) ? cur.filter(t => t !== tag) : [...cur, tag]);
},
});
})}
</x-row>
`,
})}
`;
};

renderApp(App, document.getElementById('app'));

Status alerts

import { html, renderApp } from 'mates';
import { IconButton, Text } from 'mates-ui';
import { CheckCircleIcon, CloseIcon, ErrorIcon, WarningIcon } from 'mates-icons';

const ALERTS = [
{ icon: CheckCircleIcon({ size: 16 }), msg: "Deployment succeeded.", color: "#2da44e", bg: "rgba(45,164,78,.12)" },
{ icon: WarningIcon({ size: 16 }), msg: "Memory usage above 80% on worker-3.", color: "#bf8700", bg: "rgba(191,135,0,.12)" },
{ icon: ErrorIcon({ size: 16 }), msg: "Payment gateway timeout.", color: "#cf222e", bg: "rgba(207,34,46,.12)" },
];

const App = () => () => html`
<x-col gap="var(--md-space-2)">
${ALERTS.map(({ icon, msg, color, bg }) => html`
<x-row gap="var(--md-space-2)" align="start center"
px="var(--md-space-3)" py="var(--md-space-2)"
style="border-radius:8px;background:${bg};color:${color};">
${icon}
${Text(msg, { type: "body-sm", style: { color: "inherit", flex: "1" } })}
${IconButton({ icon: CloseIcon({ size: 14 }), label: "Dismiss", variant: "standard", size: "sm", style: { color: "inherit" } })}
</x-row>
`)}
</x-col>
`;

renderApp(App, document.getElementById('app'));

User list rows

import { html, renderApp } from 'mates';
import { Avatar, Badge, Card, IconButton, Text } from 'mates-ui';
import { EditIcon, MoreVertIcon } from 'mates-icons';

const USERS = [
{ name: "Alice Johnson", role: "Engineering Lead", status: "online", badge: "Admin" },
{ name: "Ben Okafor", role: "Product Designer", status: "away", badge: "" },
{ name: "Clara Williams", role: "DevOps Engineer", status: "offline", badge: "Owner" },
{ name: "David Kim", role: "Frontend Dev", status: "online", badge: "" },
];

const App = () => () => html`
${Card({
variant: "outlined",
body: html`
<x-col>
${USERS.map((user, i, arr) => html`
<x-row align="between center" gap="var(--md-space-3)"
px="var(--md-space-4)" py="var(--md-space-3)"
style="${i < arr.length - 1 ? 'border-bottom:1px solid var(--md-color-border);' : ''}">
<x-row gap="var(--md-space-3)" style="flex:1;min-width:0;">
${Avatar({ name: user.name, size: "sm", status: user.status })}
<x-col gap="2px" style="min-width:0;">
${Text(user.name, { type: "body-sm", style: { fontWeight: "600" } })}
${Text(user.role, { type: "label-sm", color: "muted" })}
</x-col>
</x-row>
<x-row gap="var(--md-space-2)">
${user.badge ? Badge(user.badge, { variant: "primary" }) : html``}
${IconButton({ icon: EditIcon({ size: 14 }), label: "Edit", variant: "standard", size: "sm" })}
${IconButton({ icon: MoreVertIcon(), label: "More", variant: "standard", size: "sm" })}
</x-row>
</x-row>
`)}
</x-col>
`,

File attachment list

import { html, renderApp } from 'mates';
import { Badge, Card, IconButton, Text } from 'mates-ui';
import { CloseIcon, DownloadIcon } from 'mates-icons';

const FILES = [
{ name: "Q3-report.pdf", size: "2.4 MB", type: "PDF" },
{ name: "design-system.fig", size: "18 MB", type: "FIG" },
{ name: "api-schema.json", size: "340 KB", type: "JSON" },
];

const App = () => () => html`
${Card({
variant: "outlined",
body: html`
<x-col gap="var(--md-space-2)">
${FILES.map(file => html`
<x-row align="between center" gap="var(--md-space-3)"
px="var(--md-space-3)" py="var(--md-space-2)"
style="border-radius:8px;background:var(--md-surface-control);">
<x-row gap="var(--md-space-2)" style="flex:1;min-width:0;">
${Badge(file.type, { variant: "neutral" })}
<x-col gap="2px" style="min-width:0;">
${Text(file.name, { type: "body-sm" })}
${Text(file.size, { type: "label-sm", color: "muted" })}
</x-col>
</x-row>
<x-row gap="var(--md-space-1)">
${IconButton({ icon: DownloadIcon(), label: "Download", variant: "standard", size: "sm" })}
${IconButton({ icon: CloseIcon({ size: 14 }), label: "Remove", variant: "standard", size: "sm" })}
</x-row>
</x-row>
`)}
</x-col>
`,
})}
`;

Loading skeleton

import { html, renderApp } from 'mates';
import { Card, Skeleton } from 'mates-ui';

const App = () => () => html`
${Card({
variant: "outlined",
body: html`
<x-col gap="var(--md-space-3)">
${[1, 2, 3].map(() => html`
<x-row align="start center" gap="var(--md-space-3)">
${Skeleton({ variant: "circle", width: "2.25rem", height: "2.25rem" })}
<x-col gap="var(--md-space-1)" style="flex:1;">
${Skeleton({ variant: "text", width: "40%", height: "0.875rem" })}
${Skeleton({ variant: "text", width: "65%", height: "0.75rem" })}
</x-col>
${Skeleton({ variant: "rect", width: "4rem", height: "1.5rem" })}
</x-row>
`)}
</x-col>
`,
})}
`;

renderApp(App, document.getElementById('app'));

x-row

import { registerLayoutElements } from "mates-ui"; registerLayoutElements();
Prop Type Default Description
align "center" | "[jc]" | "[jc] [ai]" | "[jc] [ai] [as]" start center Space-separated shorthand. 1 value = jc (or "center" = both). 2 values = jc + ai. 3 values = jc + ai + align-self. Keywords: start, end, center, between, around, evenly, stretch, baseline.
attr AttrMap HTML attributes on the root element.
classes string | string[] Extra root classes — joined and appended after built-in classes.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
fill boolean attr false Sets width:100% and height:100% on the element.
gap any CSS length Raw CSS gap between children, e.g. `"var(--md-space-3)"` or `"1rem"`.
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
inline boolean attr false Uses inline-flex instead of flex.
on OnEventMap DOM event handlers on the root element.
p any CSS length Padding on all sides.
px any CSS length Padding inline (left + right).
py any CSS length Padding block (top + bottom).
spacing number | CSS length MUI-style spacing scale — number × 8px, e.g. `"2"` → `"16px"`. Raw CSS lengths pass through unchanged.
style StyleMap Inline CSS on the root element.
wrap boolean attr false Enables flex-wrap so children can flow onto multiple lines.