List
MD3 lists — one to three lines per row, and rows you can swipe aside for their actions.
Walkthrough
1. Lines
One, two or three lines — a name, a name and a note, a note that wraps. The row height follows, so a list stays even.
<script lang="ts"> import { Avatar, List, ListItem } from 'omaris';</script><div class="w-full max-w-md"> <List variant="outlined" dividers label="Messages"> <ListItem headline="Amina Yusuf" supportingText="Deploy is green again." lines={2}> {#snippet leading()}<Avatar name="Amina Yusuf" size="sm" colorize />{/snippet} {#snippet trailing()}<span class="text-body-sm text-muted-foreground">3m</span>{/snippet} </ListItem> <ListItem headline="Karwan Ali" supportingText="I've moved the Basra delivery to Thursday because the road is closed." lines={3} > {#snippet leading()}<Avatar name="Karwan Ali" size="sm" colorize />{/snippet} </ListItem> <ListItem headline="Hevi Salih"> {#snippet leading()}<Avatar name="Hevi Salih" size="sm" colorize />{/snippet} </ListItem> </List></div> 2. Interactive
interactive makes rows pressable, with a ripple and a selected state — a picker where the choice stays visible, like the current warehouse.
<script lang="ts"> import { List, ListItem } from 'omaris'; let chosen = $state('erbil');</script><div class="w-full max-w-md"> <List variant="surface" label="Warehouses"> <ListItem interactive selected={chosen === 'erbil'} headline="Erbil" supportingText="2 open orders" lines={2} onclick={() => (chosen = 'erbil')} /> <ListItem interactive selected={chosen === 'baghdad'} headline="Baghdad" supportingText="11 open orders" lines={2} onclick={() => (chosen = 'baghdad')} /> <ListItem interactive selected={chosen === 'basra'} headline="Basra" supportingText="No open orders" lines={2} onclick={() => (chosen = 'basra')} /> </List></div> 3. Swipe actions
Drag a row sideways — a finger, a mouse, or two fingers on a trackpad — to reveal its actions; let go and it springs to rest at the speed it had. Carry it clear across and the first action fires. dismiss collapses the row on the way out, so deleting does not leave a gap that jumps closed.
<script lang="ts"> import { List, ListItem, SwipeItem, toast } from 'omaris'; let rows = $state([ { id: 1, from: 'Amina Yusuf', subject: 'Deploy is green again' }, { id: 2, from: 'Karwan Ali', subject: 'Basra delivery moved to Thursday' } ]);</script><div class="w-full max-w-md"> <List variant="outlined" dividers label="Inbox"> {#each rows as row (row.id)} <SwipeItem fullSwipe startActions={[{ label: 'Pin', tone: 'warning', onaction: () => toast('Pinned') }]} endActions={[ { label: 'Delete', tone: 'destructive', dismiss: true, onaction: () => (rows = rows.filter((r) => r.id !== row.id)) } ]} > <ListItem headline={row.from} supportingText={row.subject} lines={2} /> </SwipeItem> {/each} </List> {#if !rows.length} <p class="p-4 text-body-sm text-muted-foreground">Everything swiped away.</p> {/if}</div> 4. Reorder
Reorder takes the container props List takes, so a list becomes reorderable by swapping the tag and binding the array. Drag a row by its grip, or tab to it and press Space, then the arrow keys. Nothing moves in the DOM while you drag — the rows are measured once and slide by exactly the space the lifted one left, so rows of different heights land where they look like they will.
Press Space to pick a row up, the arrow keys to move it, Space to drop it and Escape to put it back.
Overture · The Long Way Round · Harbour Lights · Closing Time<script lang="ts"> import { ListItem, Reorder, ReorderHandle, Text } from 'omaris'; let tracks = $state([ { id: 'a', name: 'Overture', length: '4:12' }, { id: 'b', name: 'The Long Way Round', length: '3:48' }, { id: 'c', name: 'Harbour Lights', length: '5:02' }, { id: 'd', name: 'Closing Time', length: '2:57' } ]);</script><div class="flex w-full max-w-sm flex-col gap-3"> <Reorder bind:items={tracks} variant="outlined" dividers listLabel="Playlist"> {#snippet children(track, ctx)} <ListItem role="presentation" headline={track.name} supportingText={track.length}> {#snippet leading()}<ReorderHandle />{/snippet} {#snippet trailing()} <Text variant="label-sm" tone="muted" as="span">{ctx.index + 1}</Text> {/snippet} </ListItem> {/snippet} </Reorder> <Text variant="label-sm" tone="muted"> {tracks.map((track) => track.name).join(' · ')} </Text></div>