List
A list is a bordered surface of rows. Each row has a title, an optional description, and slots at the start and end for an avatar, an icon or a control.
Basic list
- Routingsrc/about.ht.js becomes /about
- Data loadingdata() runs once, at build time
- AssetsOnly what your HTML references is bundled
list(
listItem({ title: 'Routing', description: 'src/about.ht.js becomes /about' }),
listItem({ title: 'Data loading', description: 'data() runs once, at build time' }),
listItem({ title: 'Assets', description: 'Only what your HTML references is bundled' }),
)Start and end slots
- ALAda LovelacePushed 3 commits to main2h
- GHGrace HopperOpened a pull requestopen
list(
listItem({
start: avatar({ name: 'Ada Lovelace', size: 'sm' }),
title: 'Ada Lovelace',
description: 'Pushed 3 commits to main',
end: chip({ size: 'sm', color: 'neutral' }, '2h'),
}),
listItem({
start: avatar({ name: 'Grace Hopper', size: 'sm', color: 'success' }),
title: 'Grace Hopper',
description: 'Opened a pull request',
end: chip({ size: 'sm', color: 'success', dot: true }, 'open'),
}),
)Rows that link
A row with an href puts the anchor inside the <li> rather than around it, so the list stays a valid list. Do not also put a button in the row — interactive content cannot nest inside a link.
list(
listItem({ title: 'Getting started', description: 'Install and first page', href: '/docs' }),
listItem({ title: 'Routing', description: 'File-based, with dynamic segments', href: '/docs/routing' }),
listItem({ title: 'Deployment', description: 'Netlify, Vercel, Pages, Amplify', href: '/docs/deployment' }),
)Rows with controls
When a row holds a switch or a button, leave the row itself unlinked and let the control be the interactive part.
- Pagefind searchIndexes every page at the end of the build
- Image optimizationResizes and converts images. Needs sharp.
- Server islandsRenders marked regions at request time
list(
listItem({
title: 'Pagefind search',
description: 'Indexes every page at the end of the build',
end: toggle({ 'aria-label': 'Pagefind search', checked: true }),
}),
listItem({
title: 'Image optimization',
description: 'Resizes and converts images. Needs sharp.',
end: toggle({ 'aria-label': 'Image optimization', checked: true }),
}),
listItem({
title: 'Server islands',
description: 'Renders marked regions at request time',
end: toggle({ 'aria-label': 'Server islands' }),
}),
)Plain
plain drops the border and background, for a list that sits inside a card or a sidebar that already has its own surface.
Recent builds
- 94a837amain · 4 minutes agopassed
- dcfaaaemain · 2 hours agopassed
- a46a461main · yesterdayfailed
card(
cardHeader({ title: 'Recent builds' }),
cardBody(
list({ plain: true },
listItem({ title: '94a837a', description: 'main · 4 minutes ago', end: chip({ size: 'sm', color: 'success', dot: true }, 'passed') }),
listItem({ title: 'dcfaaae', description: 'main · 2 hours ago', end: chip({ size: 'sm', color: 'success', dot: true }, 'passed') }),
listItem({ title: 'a46a461', description: 'main · yesterday', end: chip({ size: 'sm', color: 'danger', dot: true }, 'failed') }),
),
),
)Free-form rows
Without title or description, a row renders whatever children it is given — for a layout the two-line shape does not cover.
Custom row
Anything you like inside
list(
listItem(
stack({ direction: 'row', gap: 'md', align: 'center', justify: 'space-between', style: 'width: 100%' },
stack({ gap: 'none' },
text({ variant: 'small' }, 'Custom row'),
text({ variant: 'caption', tone: 'muted' }, 'Anything you like inside'),
),
button({ size: 'sm', variant: 'soft' }, 'Action'),
),
),
)From data
- EEnglish24 pagesen
- EEspañol24 pageses
- Z简体中文24 pageszh
return (() => {
const locales = [
{ code: 'en', name: 'English', pages: 24 },
{ code: 'es', name: 'Español', pages: 24 },
{ code: 'zh', name: '简体中文', pages: 24 },
]
return list(
locales.map((locale) =>
listItem({
start: avatar({ name: locale.code, size: 'sm', color: 'neutral', square: true }),
title: locale.name,
description: locale.pages + ' pages',
end: chip({ size: 'sm', color: 'neutral' }, locale.code),
}),
),
)
})()Props
list():
| Prop | Type | Default | Description |
|---|---|---|---|
plain | boolean | false | Drop the border and background. |
as | string | 'ul' | Element to render, e.g. ol. |
listItem():
| Prop | Type | Default | Description |
|---|---|---|---|
title | Child | — | The row’s main line. |
description | Child | — | A muted second line. |
start | Child | — | Leading slot — an avatar or icon. |
end | Child | — | Trailing slot — a chip, a control, a timestamp. |
href | string | — | Makes the row a link, with the anchor inside the li. |
interactive | boolean | false | Hover highlight without making it a link. |