List
Displays item collections with built-in selection and actions.
Items
Item One
Item Two
Item Three
import { List, Button } from "ui-lab-components";
export function Example() {
return (
<List aria-label="Items">
<List.Header>
<h2>Items</h2>
</List.Header>
<List.Item interactive>Item One</List.Item>
<List.Item interactive>Item Two</List.Item>
<List.Footer align="center">
<Button variant="primary" size="sm">Load More</Button>
</List.Footer>
</List>
);
}Basic List
A simple list displaying basic items with selection and interaction support.
Items
Item One
Item Two
Item Three
import { List, Button } from 'ui-lab-components';
export const metadata = {
title: 'Basic List',
description: 'A simple list displaying basic items with selection and interaction support.',
access: 'free' as const,
};
export default function Example() {
return (
<List aria-label="Basic List Example">
<List.Header>
<h2>Items</h2>
</List.Header>
<List.Item interactive>Item One</List.Item>
<List.Item interactive>Item Two</List.Item>
<List.Item interactive>Item Three</List.Item>
<List.Footer align="center">
<Button variant="primary" size="sm">
Load More
</Button>
</List.Footer>
</List>
);
}
Review Queue
A compact checklist for work that can be completed directly from each row.
Legal review
Updated retention language
Security review
New access scopes
Billing review
Invoice copy changes
"use client";
import { useState } from "react";
import { List } from "ui-lab-components";
export const metadata = {
title: "Review Queue",
description: "A compact checklist for work that can be completed directly from each row.",
access: "free" as const,
};
const reviewItems = [
{ id: "legal", title: "Legal review", desc: "Updated retention language" },
{ id: "security", title: "Security review", desc: "New access scopes" },
{ id: "billing", title: "Billing review", desc: "Invoice copy changes" },
];
export default function Example() {
const [checked, setChecked] = useState(() => new Set(["legal"]));
const setItem = (id: string, value: boolean) => {
setChecked((current) => {
const next = new Set(current);
value ? next.add(id) : next.delete(id);
return next;
});
};
const toggleItem = (id: string) => {
setChecked((current) => {
const next = new Set(current);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
};
return (
<List items={reviewItems} spacing="sm" style={{ width: 320 }}>
{reviewItems.map((item) => (
<List.Item key={item.id} value={item.id} interactive onClick={() => toggleItem(item.id)}>
<List.Checkbox
aria-label={`Mark ${item.title} reviewed`}
placement="start"
checked={checked.has(item.id)}
onCheckedChange={(value) => setItem(item.id, value)}
/>
<div className="min-w-0 flex-1">
<List.Title>{item.title}</List.Title>
<List.Desc>{item.desc}</List.Desc>
</div>
</List.Item>
))}
</List>
);
}
Notification Rules
Rows can combine a leading checkbox with a trailing Select action.
Comments
Replies and mentions
Deployments
Preview and production updates
Incidents
Status changes and postmortems
"use client";
import { useState } from "react";
import { List, Select } from "ui-lab-components";
export const metadata = {
title: "Notification Rules",
description: "Rows can combine a leading checkbox with a trailing Select action.",
access: "free" as const,
};
const notificationItems = [
{ id: "comments", title: "Comments", desc: "Replies and mentions" },
{ id: "deployments", title: "Deployments", desc: "Preview and production updates" },
{ id: "incidents", title: "Incidents", desc: "Status changes and postmortems" },
];
export default function Example() {
const [checked, setChecked] = useState(() => new Set(["comments", "incidents"]));
const [delivery, setDelivery] = useState<Record<string, string | number | null>>({
comments: "digest",
deployments: "email",
incidents: "push",
});
const setItem = (id: string, value: boolean) => {
setChecked((current) => {
const next = new Set(current);
value ? next.add(id) : next.delete(id);
return next;
});
};
const toggleItem = (id: string) => {
setChecked((current) => {
const next = new Set(current);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
};
const setDeliveryMode = (id: string, value: string | number | null) => {
setDelivery((current) => ({ ...current, [id]: value }));
};
return (
<List items={notificationItems} spacing="sm" style={{ width: 400 }}>
{notificationItems.map((item) => (
<List.Item key={item.id} value={item.id} interactive onClick={() => toggleItem(item.id)}>
<List.Checkbox
aria-label={`Enable ${item.title.toLowerCase()} notifications`}
placement="start"
checked={checked.has(item.id)}
onCheckedChange={(value) => setItem(item.id, value)}
/>
<div className="min-w-0 flex-1">
<List.Title>{item.title}</List.Title>
<List.Desc>{item.desc}</List.Desc>
</div>
<List.Select
selectedKey={delivery[item.id]}
label={String(delivery[item.id] ?? "")}
isDisabled={!checked.has(item.id)}
onSelectionChange={(value) => setDeliveryMode(item.id, value)}
>
<Select.Trigger>
<Select.Value placeholder="Mode" />
</Select.Trigger>
<Select.Content>
<Select.List>
<Select.Item value="email">Email</Select.Item>
<Select.Item value="digest">Digest</Select.Item>
<Select.Item value="push">Push</Select.Item>
</Select.List>
</Select.Content>
</List.Select>
</List.Item>
))}
</List>
);
}
Quota Editor
Inline inputs work as row actions without taking over the List primitive.
Seats
Maximum workspace members
Projects
Active projects per workspace
Tokens
Monthly API token budget
"use client";
import { useState } from "react";
import { List } from "ui-lab-components";
export const metadata = {
title: "Quota Editor",
description: "Inline inputs work as row actions without taking over the List primitive.",
access: "free" as const,
};
const quotaItems = [
{ id: "seats", title: "Seats", desc: "Maximum workspace members" },
{ id: "projects", title: "Projects", desc: "Active projects per workspace" },
{ id: "tokens", title: "Tokens", desc: "Monthly API token budget" },
];
export default function Example() {
const [checked, setChecked] = useState(() => new Set(["seats", "projects"]));
const [limits, setLimits] = useState<Record<string, string>>({
seats: "24",
projects: "12",
tokens: "50000",
});
const setItem = (id: string, value: boolean) => {
setChecked((current) => {
const next = new Set(current);
value ? next.add(id) : next.delete(id);
return next;
});
};
const toggleItem = (id: string) => {
setChecked((current) => {
const next = new Set(current);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
};
const setLimit = (id: string, value: string) => {
setLimits((current) => ({ ...current, [id]: value }));
};
return (
<List items={quotaItems} spacing="sm" style={{ width: 396 }}>
{quotaItems.map((item) => (
<List.Item key={item.id} value={item.id} interactive onClick={() => toggleItem(item.id)}>
<List.Checkbox
aria-label={`Enable ${item.title.toLowerCase()} limit`}
placement="start"
checked={checked.has(item.id)}
onCheckedChange={(value) => setItem(item.id, value)}
/>
<div className="min-w-0 flex-1">
<List.Title>{item.title}</List.Title>
<List.Desc>{item.desc}</List.Desc>
</div>
<List.Input
aria-label={`${item.title} limit`}
type="number"
value={limits[item.id]}
disabled={!checked.has(item.id)}
onChange={(event) => setLimit(item.id, event.currentTarget.value)}
className="w-24"
/>
</List.Item>
))}
</List>
);
}
Permissions Matrix
A parent checkbox can summarize rows that also expose per-row Select controls.
All permissions
Members
Invite and remove workspace members
Billing
Update plan, seats, and invoices
Tokens
Issue scoped API credentials
"use client";
import { useMemo, useState } from "react";
import { List, Select } from "ui-lab-components";
export const metadata = {
title: "Permissions Matrix",
description: "A parent checkbox can summarize rows that also expose per-row Select controls.",
access: "free" as const,
};
const permissionItems = [
{ id: "members", title: "Members", desc: "Invite and remove workspace members" },
{ id: "billing", title: "Billing", desc: "Update plan, seats, and invoices" },
{ id: "tokens", title: "Tokens", desc: "Issue scoped API credentials" },
];
export default function Example() {
const rows = useMemo(() => [{ id: "all", title: "All permissions" }, ...permissionItems], []);
const [checked, setChecked] = useState(() => new Set(["members", "tokens"]));
const [level, setLevel] = useState<Record<string, string | number | null>>({
members: "edit",
billing: "view",
tokens: "edit",
});
const allChecked = checked.size === permissionItems.length;
const isIndeterminate = checked.size > 0 && !allChecked;
const setItem = (id: string, value: boolean) => {
setChecked((current) => {
const next = new Set(current);
value ? next.add(id) : next.delete(id);
return next;
});
};
const setAll = (value: boolean) => {
permissionItems.forEach((item) => setItem(item.id, value));
};
const setPermissionLevel = (id: string, value: string | number | null) => {
setLevel((current) => ({ ...current, [id]: value }));
};
return (
<List items={rows} spacing="sm" style={{ width: 420 }}>
<List.Item value="all" interactive onClick={() => setAll(!allChecked)}>
<List.Checkbox
aria-label="Toggle all permissions"
placement="start"
checked={allChecked}
isIndeterminate={isIndeterminate}
onCheckedChange={setAll}
/>
<List.Title>All permissions</List.Title>
</List.Item>
<List.Divider />
{permissionItems.map((item) => (
<List.Item key={item.id} value={item.id} interactive onClick={() => setItem(item.id, !checked.has(item.id))}>
<div className="w-5 flex-shrink-0" />
<List.Checkbox
aria-label={`Allow ${item.title.toLowerCase()}`}
placement="start"
checked={checked.has(item.id)}
onCheckedChange={(value) => setItem(item.id, value)}
/>
<div className="min-w-0 flex-1">
<List.Title>{item.title}</List.Title>
<List.Desc>{item.desc}</List.Desc>
</div>
<List.Select
selectedKey={level[item.id]}
label={String(level[item.id] ?? "")}
isDisabled={!checked.has(item.id)}
onSelectionChange={(value) => setPermissionLevel(item.id, value)}
>
<Select.Trigger>
<Select.Value placeholder="Access" />
</Select.Trigger>
<Select.Content>
<Select.List>
<Select.Item value="view">View</Select.Item>
<Select.Item value="edit">Edit</Select.Item>
<Select.Item value="admin">Admin</Select.Item>
</Select.List>
</Select.Content>
</List.Select>
</List.Item>
))}
</List>
);
}