Lists

A list is a set of rows with text and, if needed, an icon, an avatar or a control. Rows can be read only, act on the page or navigate. What you pass decides which element is rendered.

Usage

<script lang="ts">
	import { Divider, List, ListItem } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<List style="max-width:300px">
	<ListItem>Browsers</ListItem>
	<Divider />
	<ListItem>Chrome</ListItem>
	<ListItem>Safari</ListItem>
	<ListItem supportingText="Firefox is the spiritual successor of Netscape Navigator."
		>Firefox</ListItem
	>
	<ListItem href="https://browserbench.org" target="_blank"
		>{#snippet end()}
			<Icon>open_in_new</Icon>
		{/snippet}
		{#snippet supportingText()}
			This will open a new tab
		{/snippet}Compare benchmarks</ListItem
	>
</List>

Icon item

  • Account
  • Setting
  • Sign out
<script lang="ts">
	import { Divider, List, ListItem } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<List style="max-width:300px">
	<ListItem>
		Account
		{#snippet start()}
			<Icon>person</Icon>
		{/snippet}
	</ListItem>
	<Divider />
	<ListItem>
		Setting
		{#snippet start()}
			<Icon>settings</Icon>
		{/snippet}
	</ListItem>
	<Divider />
	<ListItem>
		Sign out
		{#snippet start()}
			<Icon>logout</Icon>
		{/snippet}
	</ListItem>
</List>

Leading and trailing content

start adds a leading icon. avatar adds a 40px circle: initials as a string, or a snippet with an image. end adds a trailing icon, or a short text such as a count in a small label style.

  • Inbox
    Leading icon
  • Ada Lovelace
    Avatar with initials
    100+
  • Grace Hopper
    Avatar with an image
<script lang="ts">
	import { List, ListItem } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'

	const photo = `data:image/svg+xml,${encodeURIComponent(
		'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><rect width="40" height="40" fill="#7d5260"/><circle cx="20" cy="16" r="7" fill="#ffd8e4"/><rect x="8" y="26" width="24" height="14" rx="7" fill="#ffd8e4"/></svg>',
	)}`
</script>

<List style="max-width:340px">
	<ListItem supportingText="Leading icon">
		Inbox
		{#snippet start()}<Icon>mail</Icon>{/snippet}
		{#snippet end()}<Icon>favorite</Icon>{/snippet}
	</ListItem>
	<ListItem avatar="AL" supportingText="Avatar with initials" end="100+">Ada Lovelace</ListItem>
	<ListItem supportingText="Avatar with an image">
		Grace Hopper
		{#snippet avatar()}<img alt="" src={photo} />{/snippet}
		{#snippet end()}<Icon>verified</Icon>{/snippet}
	</ListItem>
</List>

Lines

An item takes one line, or two with supportingText: 56px and 72px high. Supporting text long enough to wrap makes it a three-line item of 88px.

  • One line
  • Two lines
    Supporting text
  • Three lines
    Supporting text that is long enough to wrap onto a second line of its own
<script lang="ts">
	import { List, ListItem } from 'noph-ui'
</script>

<List style="max-width:340px">
	<ListItem>One line</ListItem>
	<ListItem supportingText="Supporting text">Two lines</ListItem>
	<ListItem
		supportingText="Supporting text that is long enough to wrap onto a second line of its own"
	>
		Three lines
	</ListItem>
</List>

Actions

An item with an onclick is a button and acts on the page. An item with an href is a link and navigates. The actions of a list share one tab stop. Tab moves to the first one, and the arrow keys move between them.

Last picked: nothing yet

<script lang="ts">
	import { List, ListItem } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'

	let last = $state('nothing yet')
</script>

<div style="width:100%;max-width:340px">
	<List aria-label="Account">
		<ListItem onclick={() => (last = 'Profile')}>
			Profile
			{#snippet start()}<Icon>person</Icon>{/snippet}
		</ListItem>
		<ListItem onclick={() => (last = 'Settings')}>
			Settings
			{#snippet start()}<Icon>settings</Icon>{/snippet}
		</ListItem>
		<ListItem href="#actions">
			Help
			{#snippet start()}<Icon>info</Icon>{/snippet}
			{#snippet end()}<Icon>open_in_new</Icon>{/snippet}
		</ListItem>
		<ListItem disabled>
			Sign out
			{#snippet start()}<Icon>logout</Icon>{/snippet}
		</ListItem>
	</List>
	<p>Last picked: {last}</p>
</div>

Single select

selection="single" turns the list into a listbox and its items into options, so assistive technology announces which one is selected. You manage the selection: set selected on the items and change it in their onclick. Show the selection with more than color, for example a check.

<script lang="ts">
	import { List, ListItem } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'

	const places = ['Home', 'Work', 'Coffee shop']
	let place = $state('Work')
</script>

<List selection="single" aria-label="Pick a place" style="max-width:340px">
	{#each places as option (option)}
		<ListItem selected={place === option} onclick={() => (place = option)}>
			{option}
			{#snippet end()}
				{#if place === option}<Icon>check</Icon>{/if}
			{/snippet}
		</ListItem>
	{/each}
</List>

Multi select

selection="multiple" works the same way, but any number of items can be selected.

<script lang="ts">
	import { List, ListItem } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'

	const topics = ['Mail', 'Calendar', 'Reminders', 'Updates']
	let chosen = $state(['Mail'])

	const toggle = (topic: string) =>
		(chosen = chosen.includes(topic) ? chosen.filter((t) => t !== topic) : [...chosen, topic])
</script>

<List selection="multiple" aria-label="Notify me about" style="max-width:340px">
	{#each topics as topic (topic)}
		<ListItem selected={chosen.includes(topic)} onclick={() => toggle(topic)}>
			{topic}
			{#snippet start()}
				<!-- The check keeps its place when hidden, so the labels stay in line. -->
				<span style:visibility={chosen.includes(topic) ? 'visible' : 'hidden'}>
					<Icon>check</Icon>
				</span>
			{/snippet}
		</ListItem>
	{/each}
</List>

Theming

Tokens

TokenDefault value
--np-item-container-height3.5rem, or 4.5rem with supporting text
--np-item-gap1rem
--np-item-avatar-container-color--np-color-primary-container
--np-item-avatar-label-text-color--np-color-on-primary-container
--np-item-label-text-color--np-color-on-surface
--np-item-supporting-text-color--np-color-on-surface-variant
--np-item-leading-icon-color--np-color-on-surface-variant
--np-item-trailing-icon-color--np-color-on-surface-variant

--np-item-container-height sets the minimum height of an item. An item with taller content grows past it.

Example

  • Account
<script lang="ts">
	import { List, ListItem } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<List style="max-width:300px">
	<ListItem
		--np-item-container-height="3rem"
		--np-item-label-text-color="var(--np-color-tertiary)"
		--np-item-leading-icon-color="var(--np-color-tertiary)"
	>
		Account
		{#snippet start()}
			<Icon>person</Icon>
		{/snippet}
	</ListItem>
</List>

Accessibility

List renders a plain <ul>, so anything you put inside becomes a list item for assistive technology. Give an item an href when it navigates, an onclick when it acts on the current page, and neither when it is only read. This decides the rendered element, its role and whether it can get focus.

The actions of a list share one tab stop. It is on the selected item, or on the first one if nothing is selected. ↓ and → move to the next action, ↑ and ← to the previous one, wrapping at the ends. Home and End jump to the first and last. Space and Enter activate an item, links included.

With selection, the list is a listbox and each item is an option with aria-selected. It adds aria-multiselectable for multiple. Give the list an aria-label that says what is being chosen.

A disabled item renders as a <div> with aria-disabled instead of a disabled control. Screen readers can still read it, but it is not in the tab order.

API

List attributes

All other attributes go to the <ul> element, including class, style and event handlers.

AttributeTypeDefaultDescription
elementHTMLUListElement | undefinedundefinedBindable reference to the underlying <ul>.
selection'single' | 'multiple' | undefinedundefinedMakes the list a listbox of options that announce their selected state.

Item

ListItem is an Item wrapped in an <li>. When a row is not in a <ul>, for example in search results, a sheet or a card, use Item instead. It takes the same attributes as ListItem below.

The item picks its element from what you pass: an href makes it an <a>. An onclick, a command or a popovertarget makes it a <button>. Without any of them it is a <div>. Links and buttons have a ripple. Set type to choose the element yourself, for example type="text" for a row where only the controls inside it react to clicks.

<script lang="ts">
	import { Item } from 'noph-ui'
</script>

<Item href="/components/list">Lists</Item>

ListItem attributes

AttributeTypeDefaultDescription
variant'text' | 'button' | 'link' | undefinedundefinedDeprecated, use type. Still pins the element: a <div>, a <button> or an <a>.
selectedbooleanfalseWhether the item is shown as selected.
disabledbooleanfalseRenders the item as non-interactive with aria-disabled, even with an href or onclick.
supportingTextstring | Snippet | undefinedundefinedSecond line below the label. Pass a snippet to render your own markup. An item with supporting text is taller.
startSnippet | undefinedundefinedLeading content, usually an icon or an avatar.
avatarstring | Snippet | undefinedundefinedA 40px round avatar in place of start: initials, or an image in a snippet.
endstring | Snippet | undefinedundefinedTrailing content, usually an icon, a switch or a checkbox. Pass a string for short trailing text such as a count.
softFocusbooleanfalseShows the hover state without hover. Use it to mark the active option in a list controlled by the arrow keys.
lazybooleanfalseSkips rendering the item while it is off screen, for very long lists.
type'text' | 'link' | 'button' | 'submit' | 'reset' | nullundefinedPins the element, like on Card. text renders a <div>, link an <a>, and the native button types a <button> of that type. Use submit to have the item submit the surrounding form.