tp-list holds tp-list-item rows and tp-list-separator dividers. Both are layout only: there is no selection, activation or keyboard model of their own. Controls in the actions slot keep their own focus and behavior, so one row can hold several independent actions.
Content and actions
The default slot is the title and description adds supporting text; use the title and description slots when either needs markup. Empty regions collapse, so a title-only row needs nothing extra.
Content and actions
Default density
Compact density
Restart from the initial state
Show the copyable source
Two-factor authentication
On
MacBook Pro
Sign out
Pro plan
DowngradeUpgrade
Two-factor authentication
On
MacBook Pro
Sign out
Pro plan
DowngradeUpgrade
<div style="display:grid;gap:var(--tp-space-3);max-inline-size:30rem;margin-inline:auto;inline-size:100%">
<tp-list-item variant="outline">Two-factor authentication
<tp-badge slot="actions" variant="secondary">On</tp-badge>
</tp-list-item>
<tp-list-item variant="outline" description="Signed in from Chrome on macOS, Berlin.">
MacBook Pro
<tp-button slot="actions" variant="outline" size="sm">Sign out</tp-button>
</tp-list-item>
<tp-list-item variant="outline" description="Your plan renews on March 1. Downgrading takes effect then.">
Pro plan
<tp-button slot="actions" variant="ghost" size="sm">Downgrade</tp-button>
<tp-button slot="actions" size="sm">Upgrade</tp-button>
</tp-list-item>
</div>
Variants and sizes
variant is ghost, outline or subdued; size steps the density down through sm and xs for tight panels and menus.
Variants and sizes
Default density
Compact density
Restart from the initial state
Show the copyable source
<div style="display:grid;gap:var(--tp-space-3);max-inline-size:30rem;margin-inline:auto;inline-size:100%">
<tp-list-item description="Ghost is the default.">Release notes</tp-list-item>
<tp-list-item variant="outline" description="Outline draws a boundary.">Release notes</tp-list-item>
<tp-list-item variant="subdued" description="Subdued fills the row.">Release notes</tp-list-item>
<tp-list-item variant="outline" size="sm" description="Small">Release notes</tp-list-item>
<tp-list-item variant="outline" size="xs" description="Extra small">Release notes</tp-list-item>
</div>
Media
The media slot takes an Icon, Avatar or image. media-treatment tells the row what it holds, icon or image, so the media gets the matching extent. With a description, media aligns to the first line of text.
Media
Default density
Compact density
Restart from the initial state
Show the copyable source
tp-list carries the list role and its aria-label on the host, and its direct tp-list-item rows become list items. Name every list. Put tp-list-separator between rows for a divided list. A single tp-list-item also works on its own, outside a list, as in the examples above. A row that leaves the list, or a list that leaves the page, releases only the roles the list set; authored roles are preserved, and a standalone tp-list-item has no list item role. Ordinary tp-separator children are also supported; the list registers their presentation as list-separator, while their rendering contract remains on the Separator component.
Divided list
Default density
Compact density
Restart from the initial state
Show the copyable source
Email
Push
Weekly digest
Email
Push
Weekly digest
<tp-list aria-label="Notification channels" style="max-inline-size:30rem;margin-inline:auto;inline-size:100%">
<tp-list-item description="Mentions, replies and direct messages.">
Email
<tp-switch slot="actions" label="Email notifications" default-checked></tp-switch>
</tp-list-item>
<tp-list-separator></tp-list-separator>
<tp-list-item description="Only when you are away from the desktop app.">
Push
<tp-switch slot="actions" label="Push notifications" default-checked></tp-switch>
</tp-list-item>
<tp-list-separator></tp-list-separator>
<tp-list-item description="A summary of activity every Monday.">
Weekly digest
<tp-switch slot="actions" label="Weekly digest"></tp-switch>
</tp-list-item>
</tp-list>
Header and footer
header and footer span the full row above and below the main content, for an eyebrow label or metadata.
Header and footer
Default density
Compact density
Restart from the initial state
Show the copyable source
Marketing site
Website refresh
OpenUpdated · 6 contributors
Marketing site
Website refresh
OpenUpdated · 6 contributors
Header media works too: a square Aspect ratio image in header turns each row into a card. Lay the list out as a grid through the list part’s partPresentation.
Card grid
Default density
Compact density
Restart from the initial state
Show the copyable source
A whole row can be a native link: give the list-item part a renderDelegate that renders an a around the supplied bind and content. Controls in actions render beside the link, so they stay separate tab stops; decorative icons there let clicks reach the link. Put independent controls in actions (or trailing), not in the title, description, media, header or footer of an interactive Root. Native Tab order reaches Root before its actions; activating an action does not activate Root.
Link rows
Default density
Compact density
Restart from the initial state
Show the copyable source
Getting started guide
Billing questions
Contact sales
System status
Getting started guide
Billing questions
Contact sales
System status
<tp-list id="list-links" aria-label="Help resources" style="max-inline-size:30rem;margin-inline:auto;inline-size:100%">
<tp-list-item variant="outline" data-href="#links" description="Set up your workspace in ten minutes.">
Getting started guide
<tp-icon slot="actions" data-icon="chevron"></tp-icon>
</tp-list-item>
<tp-list-item variant="outline" data-href="#links" description="Invoices, seats and tax settings.">
Billing questions
<tp-button slot="actions" variant="outline" size="sm">Contact sales</tp-button>
</tp-list-item>
<tp-list-item variant="outline" data-href="https://status.example.com/" data-external description="Opens the status page in a new tab.">
System status
<tp-icon slot="actions" data-icon="external"></tp-icon>
</tp-list-item>
</tp-list>
<script type="module">
import { html } from 'lit';
const icons = {
chevron: { viewBox: '0 0 24 24', paths: [{ d: 'm9 18 6-6-6-6', strokeWidth: 2 }] },
external: { viewBox: '0 0 24 24', paths: [{ d: 'M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6', strokeWidth: 2 }] },
};
for (const item of document.querySelectorAll('#list-links tp-list-item')) {
const external = item.hasAttribute('data-external');
item.partContracts = {
'list-item': {
renderDelegate: ({ bind, content }) => html`<a href=${item.dataset.href} target=${external ? '_blank' : '_self'} rel=${external ? 'noopener noreferrer' : ''} ${bind}>${content}</a>`,
},
};
}
for (const icon of document.querySelectorAll('#list-links tp-icon')) icon.icon = icons[icon.dataset.icon];
</script>
Custom content layout
Each part’s renderDelegate can also wrap its content, for example to add a trailing duration column to the list-item-content part. With media-treatment="image", an img in media gets a square thumbnail.
Recordings
Default density
Compact density
Restart from the initial state
Show the copyable source
Designing for density · Day 1Accessible data tables · Day 1Motion with restraint · Day 2
Designing for density · Day 1Accessible data tables · Day 1Motion with restraint · Day 2
<tp-list id="list-recordings" aria-label="Workshop recordings" style="max-inline-size:30rem;margin-inline:auto;inline-size:100%">
<tp-list-item variant="outline" media-treatment="image" description="Maya Chen" data-duration="42:10">
<img slot="media" src="https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=96&h=96&q=75&auto=format&fit=crop" alt="">
<span slot="title">Designing for density <span style="color:var(--tp-muted-foreground)">· Day 1</span></span>
</tp-list-item>
<tp-list-item variant="outline" media-treatment="image" description="Jordan Lee" data-duration="35:48">
<img slot="media" src="https://images.unsplash.com/photo-1497215728101-856f4ea42174?w=96&h=96&q=75&auto=format&fit=crop" alt="">
<span slot="title">Accessible data tables <span style="color:var(--tp-muted-foreground)">· Day 1</span></span>
</tp-list-item>
<tp-list-item variant="outline" media-treatment="image" description="Ana Ruiz" data-duration="51:02">
<img slot="media" src="https://images.unsplash.com/photo-1548516173-3cabfa4607e9?w=96&h=96&q=75&auto=format&fit=crop" alt="">
<span slot="title">Motion with restraint <span style="color:var(--tp-muted-foreground)">· Day 2</span></span>
</tp-list-item>
</tp-list>
<script type="module">
import { html } from 'lit';
for (const item of document.querySelectorAll('#list-recordings tp-list-item')) {
item.partContracts = {
'list-item-content': {
renderDelegate: ({ bind, content }) => html`<span ${bind} style="display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:var(--tp-space-3);align-items:center">
<span style="display:flex;flex-direction:column;gap:inherit;min-inline-size:0">${content}</span>
<span style="color:var(--tp-muted-foreground);font-variant-numeric:tabular-nums">${item.dataset.duration}</span>
</span>`,
},
};
}
</script>
In a menu
For a rich command, put a List item directly inside a Menu item. The Menu item keeps the command semantics, keyboard navigation and highlight, and the row drops its own padding. Do not add a second link or button inside that row.
People picker
Default density
Compact density
Restart from the initial state
Show the copyable source
AttributePropertyTypeDefaultDescriptionaria-labelariaLabelstring | nullnullThe name of the list, which assistive technology reads before its rows.
Slots
SlotDescriptiondefaultThe List items, with List separators between them where rows need a divider.
CSS parts
PartDescription::part(list)The stack holding the rows.::part(list-separator)The divider between rows.
<tp-list-item>
Attributes
AttributePropertyTypeDefaultDescriptionselectedselectedbooleanfalseMarks the row as the current one with aria-current; the List keeps no selection of its own.valuevaluestring''An identity for the application's own use; it changes nothing in the row.descriptiondescriptionstring''Supporting text under the title, when the description slot is empty.variantvariant'ghost' | 'outline' | 'subdued''ghost'Visual treatment only: outline draws a boundary and subdued fills the row.sizesize'xs' | 'sm' | 'default''default'The row density; sm and xs suit tight panels and menus.media-treatmentmediaTreatment'plain' | 'icon' | 'image''plain'What the media slot holds, so it gets the matching extent: icon for an Icon, image for a square thumbnail.
Slots
SlotDescriptiondefaultThe title.titleA title with markup; it replaces the default slot.descriptionSupporting text with markup; it replaces description.mediaAn Icon, an Avatar or an image before the text.leadingThe former name of media, used when media is empty.actionsControls at the end of the row, each with its own focus and behavior.trailingThe former name of actions, used when actions is empty.headerFull-width content above the row, such as an eyebrow label or an image.footerFull-width content below the row, such as metadata.
CSS parts
PartDescription::part(list-item)The row: the native host a link or button delegate replaces, and the host of the variant appearance.::part(list-item-actions)The wrapper of the actions slot, beside the row so its controls stay separate tab stops.::part(list-item-content)The column holding the title and the description.::part(list-item-description)The wrapper of the description, in muted text.::part(list-item-title)The wrapper of the title.
<tp-list-separator>
CSS parts
PartDescription::part(list-separator)The divider between rows.
Related
Components, widgets and patterns that use this one.