List
Bullet, number and check lists, and CustomList with markers.
On this page
List and ListItem render bullet, numbered and check lists whose markers, spacing and colors come from the list tokens. CustomList lays out items with a marker of your choice, such as an icon or an emoji, in a two-column grid.
- Tokens exported from Figma as DTCG JSON
- CSS variables and Tailwind utilities generated on sync
- Typed React and Next.js components that read them
import { List, ListItem } from '@systhemaui/next'
export default function Demo() {
return (
<List type="bullet">
<ListItem>Tokens exported from Figma as DTCG JSON</ListItem>
<ListItem>CSS variables and Tailwind utilities generated on sync</ListItem>
<ListItem>Typed React and Next.js components that read them</ListItem>
</List>
)
}ImportLink to this section
import {
CustomList,
CustomListContent,
CustomListItem,
CustomListItemMarker,
List,
ListItem,
} from '@systhemaui/next'In a React app without Next.js, import them from @systhemaui/react. The API is the same.
Variants and tagsLink to this section
The type prop picks the marker. bullet (the default) and check render a <ul>, number renders an <ol>. Use as to choose another element.
bullet
- Header
- Hero
- Footer
number
- Install
- Configure
- Sync
check
- Checked Tokens
- Checked Components
- Unchecked Content
import { List, ListItem, Paragraph } from '@systhemaui/next'
export default function Demo() {
return (
<div className="grid w-full gap-8 sm:grid-cols-3">
<div>
<Paragraph type="label">bullet</Paragraph>
<List type="bullet">
<ListItem>Header</ListItem>
<ListItem>Hero</ListItem>
<ListItem>Footer</ListItem>
</List>
</div>
<div>
<Paragraph type="label">number</Paragraph>
<List type="number">
<ListItem>Install</ListItem>
<ListItem>Configure</ListItem>
<ListItem>Sync</ListItem>
</List>
</div>
<div>
<Paragraph type="label">check</Paragraph>
<List type="check">
<ListItem checked>Tokens</ListItem>
<ListItem checked>Components</ListItem>
<ListItem checked={false}>Content</ListItem>
</List>
</div>
</div>
)
}With the default tokens, bullets are en dashes, numbers are followed by a period, and check items show a check mark or a cross.
ExamplesLink to this section
Check listsLink to this section
In a check list, checked decides the marker: true shows the check mark, false (or no value) the cross. When checked is set, the item starts with visually hidden "Checked" or "Unchecked" text, so screen readers announce the state. Change the words with checkedLabel and uncheckedLabel, for example to translate them.
- Inbegriffen Unbegrenzte Seiten
- Inbegriffen Eigene Domain
- Nicht inbegriffen Mehrsprachige Inhalte
import { List, ListItem } from '@systhemaui/next'
export default function Demo() {
return (
<List type="check">
<ListItem checked checkedLabel="Inbegriffen">
Unbegrenzte Seiten
</ListItem>
<ListItem checked checkedLabel="Inbegriffen">
Eigene Domain
</ListItem>
<ListItem checked={false} uncheckedLabel="Nicht inbegriffen">
Mehrsprachige Inhalte
</ListItem>
</List>
)
}Change the two icons with blocks.list.checkedIcon and blocks.list.uncheckedIcon (see Component CSS blocks).
Nested listsLink to this section
Put a nested list in its own ListItem, the way rich text from the Lexical editor arrives. That item shows no marker and does not count in a numbered list, so the numbering continues after it. Mark the inner items nested so they do not run their own scroll reveal inside the parent's.
- Install the packages
- @systhemaui/core
- @systhemaui/next
- Run the first sync
- Start the dev server
import { List, ListItem } from '@systhemaui/next'
export default function Demo() {
return (
<List type="number">
<ListItem>Install the packages</ListItem>
<ListItem>
<List type="bullet">
<ListItem nested>@systhemaui/core</ListItem>
<ListItem nested>@systhemaui/next</ListItem>
</List>
</ListItem>
<ListItem>Run the first sync</ListItem>
<ListItem>Start the dev server</ListItem>
</List>
)
}Custom markersLink to this section
CustomList is a two-column grid: CustomListItemMarker in the first column, CustomListContent in the second. The marker is sized from --list-marker-size and takes the list marker color, so an emoji, a character or an icon all line up.
- Fluid spacing that scales with the viewport
- Two color systems, switchable per section
- Payload blocks that render the same components
import {
CustomList,
CustomListContent,
CustomListItem,
CustomListItemMarker,
} from '@systhemaui/next'
const features = [
{ marker: '⚡', text: 'Fluid spacing that scales with the viewport' },
{ marker: '🎨', text: 'Two color systems, switchable per section' },
{ marker: '🧩', text: 'Payload blocks that render the same components' },
]
export default function Demo() {
return (
<CustomList>
{features.map((feature) => (
<CustomListItem key={feature.text}>
<CustomListItemMarker aria-hidden="true">{feature.marker}</CustomListItemMarker>
<CustomListContent>{feature.text}</CustomListContent>
</CustomListItem>
))}
</CustomList>
)
}For an SVG marker, give the <svg> a size of 1em; the marker sets its font size to --list-icon-size.
- Free updates for a year
- Priority support
- Unlimited projects
import {
CustomList,
CustomListContent,
CustomListItem,
CustomListItemMarker,
} from '@systhemaui/next'
export default function Demo() {
return (
<CustomList>
{['Free updates for a year', 'Priority support', 'Unlimited projects'].map((text) => (
<CustomListItem key={text}>
<CustomListItemMarker>
<svg
width="1em"
height="1em"
viewBox="0 -960 960 960"
fill="currentColor"
aria-hidden="true"
focusable="false"
>
<path d="m233-120 65-281L80-590l288-25 112-265 112 265 288 25-218 189 65 281-247-149-247 149Z" />
</svg>
</CustomListItemMarker>
<CustomListContent>{text}</CustomListContent>
</CustomListItem>
))}
</CustomList>
)
}PropsLink to this section
ListLink to this section
List takes the props of the element it renders (<ul> or <ol>, or the as element).
| Prop | Type | Default | Description |
|---|---|---|---|
as | ElementType | 'ul' | |
type | 'bullet' | 'number' | 'check' | - | |
className | string | - | |
…and all <ul> attributes |
ListItemLink to this section
checked is only meaningful in a check list. checkedLabel defaults to 'Checked' and uncheckedLabel to 'Unchecked'; they are rendered only when checked is set. nested and disableAnimation both drop the configured animationClasses.
| Prop | Type | Default | Description |
|---|---|---|---|
as | ElementType | 'li' | |
nested | boolean | - | |
className | string | - | |
disableAnimation | boolean | - | |
checked | boolean | - | |
checkedLabel | string | - | |
uncheckedLabel | string | - | |
…and all <li> attributes |
Custom list partsLink to this section
CustomList renders a <ul> and takes disableAnimation. CustomListItem renders an <li>, CustomListItemMarker and CustomListContent render <span>s; each takes as for another element.
| Prop | Type | Default | Description |
|---|---|---|---|
as | ElementType | 'ul' | |
className | string | - | |
disableAnimation | boolean | - | |
…and all <ul> attributes |
| Prop | Type | Default | Description |
|---|---|---|---|
as | ElementType | 'li' | |
className | string | - | |
…and all <li> attributes |
| Prop | Type | Default | Description |
|---|---|---|---|
as | ElementType | 'span' | |
className | string | - | |
…and all <span> attributes |
| Prop | Type | Default | Description |
|---|---|---|---|
as | ElementType | 'span' | |
className | string | - | |
…and all <span> attributes |
HTML and CSSLink to this section
Without React, put a list class on the <ul> or <ol>. Markers are ::before pseudo-elements, so the items need no extra markup; add is-checked to a checked item in a check list:
<ul class="list-bullet text-body color-body">
<li>Header</li>
<li>Footer</li>
</ul>
<ol class="list-number text-body color-body">
<li>Install</li>
<li>Sync</li>
</ol>
<ul class="list-check text-body color-body">
<li class="is-checked"><span class="sr-only!">Checked </span>Tokens</li>
<li><span class="sr-only!">Unchecked </span>Content</li>
</ul>
<ul class="list-custom text-body color-body">
<li class="list-custom-entry">
<span class="list-custom-entry-marker" aria-hidden="true">⚡</span>
<span class="list-entry-custom-content">Fluid spacing</span>
</li>
</ul>The marker size, gaps and color come from the list tokens (--list-marker-size, --list-icon-size, --list-space-x, --list-gap-y, --color-list-marker). The list classes also suppress the native marker, so a project rule that sets list-style-type cannot show two markers.
| Class | Styles |
|---|---|
list-bullet | :is(.list-bullet > li, .list-number > li, .list-check > li) { list-style: none; position: relative; padding-left: calc(var(--list-space-x) + var(--list-marker-size)); margin-block: var(--list-gap-y); }:is(.list-bullet > li :first-child, .list-number > li :first-child, .list-check > li :first-child) { margin-top: 0; }:is(.list-bullet > li :last-child, .list-number > li :last-child, .list-check > li :last-child) { margin-bottom: 0; }+ 4 more rules |
list-number | :is(.list-bullet > li, .list-number > li, .list-check > li) { list-style: none; position: relative; padding-left: calc(var(--list-space-x) + var(--list-marker-size)); margin-block: var(--list-gap-y); }:is(.list-bullet > li :first-child, .list-number > li :first-child, .list-check > li :first-child) { margin-top: 0; }:is(.list-bullet > li :last-child, .list-number > li :last-child, .list-check > li :last-child) { margin-bottom: 0; }+ 7 more rules |
list-check | :is(.list-bullet > li, .list-number > li, .list-check > li) { list-style: none; position: relative; padding-left: calc(var(--list-space-x) + var(--list-marker-size)); margin-block: var(--list-gap-y); }:is(.list-bullet > li :first-child, .list-number > li :first-child, .list-check > li :first-child) { margin-top: 0; }:is(.list-bullet > li :last-child, .list-number > li :last-child, .list-check > li :last-child) { margin-bottom: 0; }+ 7 more rules |
is-checked | :is(.list-check > .is-checked)::before { mask-image: url("data:image/svg+xml,…"); } |
list-custom | & { display: grid; align-items: start; grid-template-columns: auto 1fr; column-gap: var(--list-space-x); row-gap: var(--list-gap-y); }:is(.list-custom > li:has( > :is(ul, ol))):before { --tw-content: none; } |
list-custom-entry | display: contents; |
list-custom-entry-marker | grid-column-start: 1;width: var(--list-marker-size);height: var(--list-marker-size);display: inline-grid;place-items: center;font-size: var(--list-icon-size);line-height: 1;color: var(--color-list-marker); |
list-entry-custom-marker | :is(.list-entry-custom-marker > *) { width: 100%; height: 100%; }:is(.list-entry-custom-marker > img, .list-entry-custom-marker > svg) { object-fit: contain; object-position: center; } |
list-entry-custom-content | grid-column-start: 2; |
Next.jsLink to this section
@systhemaui/next re-exports the list components from @systhemaui/react unchanged. They are server-compatible.
AccessibilityLink to this section
- Bullet and number markers are CSS, and the elements are real
<ul>,<ol>and<li>, so screen readers announce the list and its length. - A check mark is a picture, not text. Set
checkedon every item of a check list so the state is announced, and translatecheckedLabelanduncheckedLabelwith the page. - Hide a decorative custom marker with
aria-hidden="true".
See Lists and counters.