Listview

Listview displays structured rows of content with optional icons, titles, subtitles, and trailing actions. It is a pure CSS component — no JavaScript required. Apply .listview to a <ul>, <ol>, or <div> and compose each row with the provided slot classes.

Listview is suited for navigation menus, settings panels, file browsers, contact lists, onboarding flows, and any interface that needs scannable rows with consistent structure.


Getting Started

Apply .listview to a list container and .list-item to each row. Use the three slot classes — .list-start, .list-content, and .list-end — to compose the row layout.

<ul class="listview">
  <li class="list-item">
    <div class="list-start">
      <span class="list-icon">★</span>
    </div>
    <div class="list-content">
      <span class="list-title">Item Title</span>
      <span class="list-subtitle">Supporting description text</span>
    </div>
    <div class="list-end">
      <span>12:30</span>
    </div>
  </li>
</ul>
  • Item TitleSupporting description text
    12:30

Structure

Listview is built on a flexible three-slot system. Each .list-item supports a left slot, a center content slot, and a right slot — use only the slots you need.

ElementClass / AttributePurpose
ContainerlistviewRoot class. Apply to <ul>, <ol>, or <div>.
Rowlist-itemRow element. Flex row with space-between alignment and bottom border.
Left slotlist-startFixed-width left slot for icons, avatars, or checkboxes. Never compresses.
Center slotlist-contentTakes all remaining space via flex: 1. Holds title and subtitle.
Primary textlist-titleMain label inside .list-content. 500 weight, single-line ellipsis.
Secondary textlist-subtitleSupporting text inside .list-content. Muted color, 0.875rem.
Right slotlist-endFixed-width right slot for metadata, badges, or actions.
Icon helperlist-iconSizes icons to 1.25rem with line-height: 1. Use inside .list-start or .list-end.

State Modifiers

State modifiers are applied to individual .list-item elements.

ModifierApplies toEffect
is-active.list-itemFills the row with --primary. Text color auto-calculated for contrast via oklch.
is-disabled.list-itemApplies opacity: 0.6 and pointer-events: none. Also works on <a> elements.

is-active

Highlights the row as selected. The background fills with --primary, and text color is automatically calculated for contrast via oklch. Subtitle, start, and end slots shift to 75% opacity of the same contrast color.

<ul class="listview is-bordered">
  <li class="list-item">Inbox</li>
  <li class="list-item is-active">Sent</li>
  <li class="list-item">Drafts</li>
</ul>
  • Inbox
  • Sent
  • Drafts

is-disabled

Makes the row non-interactive. Applies opacity: 0.6 and pointer-events: none. Works on both <li> and <a> elements.

<ul class="listview is-bordered">
  <li class="list-item">Available option</li>
  <li class="list-item is-disabled">Unavailable option</li>
</ul>
  • Available option
  • Unavailable option

.is-disabled uses pointer-events: none, which prevents mouse interaction but does not remove the element from the tab order. For full accessibility, also add aria-disabled="true" and tabindex="-1" to disabled items.


Style Modifiers

Style modifiers are applied to the .listview container.

ModifierEffect
is-borderedAdds a visible oklch(50% 0 0 / 0.12) border around the entire container.
is-borderlessRemoves the container border and every row's bottom divider. Useful inside cards or panels.
is-hoverableEnables subtle hover background and cursor: pointer. Disabled rows are excluded automatically.
is-stripedApplies oklch(50% 0 0 / 0.04) tint to every even-numbered row.
is-numberedPrepends an incrementing CSS counter to each row. Renders in --primary at font-weight: 600.

is-bordered

Adds a visible border around the entire container. By default the container has a transparent border.

<ul class="listview is-bordered">
  <li class="list-item">First item</li>
  <li class="list-item">Second item</li>
</ul>
  • First item
  • Second item

is-borderless

Removes all borders — both the container border and every row's bottom divider. Useful for lists embedded inside cards or panels.

<ul class="listview is-borderless">
  <li class="list-item">No dividers</li>
  <li class="list-item">No borders</li>
</ul>
  • No dividers
  • No borders

is-hoverable

Enables a subtle hover background (oklch(from var(--body-text) l c h / 0.05)) and sets cursor: pointer. Rows with .is-disabled are automatically excluded. Active rows get a darker oklch shade on hover.

<ul class="listview is-bordered is-hoverable">
  <li class="list-item">Hover me</li>
  <li class="list-item is-active">Active row</li>
  <li class="list-item is-disabled">Cannot hover</li>
</ul>
  • Hover me
  • Active row
  • Cannot hover

is-striped

Applies a tint to every even-numbered row for a zebra-stripe pattern.

<ul class="listview is-striped is-bordered">
  <li class="list-item">Row 1 — default</li>
  <li class="list-item">Row 2 — tinted</li>
  <li class="list-item">Row 3 — default</li>
  <li class="list-item">Row 4 — tinted</li>
</ul>
  • Row 1 — default
  • Row 2 — tinted
  • Row 3 — default
  • Row 4 — tinted

is-numbered

Automatically prepends an incrementing counter to each row using CSS counter-reset and counter-increment. No manual numbering is needed in markup. The counter renders in --primary at font-weight: 600.

<ul class="listview is-numbered is-bordered">
  <li class="list-item">Create your account</li>
  <li class="list-item">Verify your email</li>
  <li class="list-item">Complete your profile</li>
</ul>
  • Create your account
  • Verify your email
  • Complete your profile

Style modifiers can be combined freely. is-hoverable is-bordered is-striped is a valid combination.


Size Modifiers

Size modifiers are applied to individual .list-item elements — not the container — allowing mixed-density rows within the same list.

ClassPadding
default0.875rem 1rem
is-medium1.125rem 1.25rem
is-large1.375rem 1.5rem
<ul class="listview is-bordered">
  <li class="list-item">Default padding</li>
  <li class="list-item is-medium">Medium padding</li>
  <li class="list-item is-large">Large padding</li>
</ul>
  • Default padding
  • Medium padding
  • Large padding

Using Links as Items

Apply .list-item directly to an <a> element for a fully clickable navigation row. The entire row becomes the tap and click target — no wrapper div needed.

<ul class="listview is-bordered is-hoverable">
  <a class="list-item" href="/profile">
    <div class="list-start">
      <span class="list-icon">👤</span>
    </div>
    <div class="list-content">
      <span class="list-title">Profile</span>
      <span class="list-subtitle">Manage your account</span>
    </div>
    <div class="list-end">›</div>
  </a>

  <a class="list-item" href="/settings">
    <div class="list-start">
      <span class="list-icon">⚙️</span>
    </div>
    <div class="list-content">
      <span class="list-title">Settings</span>
    </div>
    <div class="list-end">›</div>
  </a>

  <a class="list-item is-disabled" href="/billing">
    <div class="list-content">
      <span class="list-title">Billing</span>
      <span class="list-subtitle">Unavailable on your plan</span>
    </div>
  </a>
</ul>

CSS Custom Properties

VariableUsed byDescription
var(--primary)is-active background, is-numbered counterPrimary brand color token.
var(--surface)Container and row backgroundSurface color token — adapts to light/dark theme automatically.
var(--body-text)Row text, hover tintPrimary text color token.
var(--muted).list-start, .list-subtitle, .list-endSecondary text color token.

Accessibility

  • Use <ul> or <ol> as the container for semantic list markup.
  • For interactive rows, use <a> or <button> as .list-item rather than <li> with click handlers.
  • Add role="listbox" and role="option" when the listview is used as a selection widget.
  • For disabled items, add aria-disabled="true" and tabindex="-1" alongside .is-disabled — CSS pointer-events: none alone does not prevent keyboard focus.
  • Always provide a visible or accessible label for icon-only .list-start slots via aria-label or a visually hidden <span>.

Class Reference

ClassApplies toRole
listview<ul>, <ol>, <div>Root container. Sets flex column layout, surface background, border radius.
list-item<li>, <a>, <div>Row element. Flex row with space-between alignment and bottom border.
list-startChild of list-itemLeft slot. Fixed width, muted color, right margin.
list-contentChild of list-itemCenter slot. Flex column, takes all available space.
list-titleChild of list-contentPrimary text. 500 weight, single-line ellipsis.
list-subtitleChild of list-contentSecondary text. Muted color, single-line ellipsis.
list-endChild of list-itemRight slot. Fixed width, muted color, left margin.
list-iconChild of list-start or list-endIcon sizing helper. 1.25rem font size, line-height: 1.
is-activelist-itemSelected state. Primary background, auto-contrast text via oklch.
is-disabledlist-itemDisabled state. 60% opacity, pointer-events none.
is-borderedlistviewVisible container border.
is-borderlesslistviewRemoves container border and all row dividers.
is-hoverablelistviewHover background and pointer cursor on rows.
is-stripedlistviewZebra-stripe tint on even rows.
is-numberedlistviewCSS counter prefix on each row. Renders in --primary at font-weight: 600.
is-mediumlist-itemMedium row padding: 1.125rem 1.25rem.
is-largelist-itemLarge row padding: 1.375rem 1.5rem.

Notes

  • Listview requires no JavaScript — CSS-only for all variants and states.
  • .list-start, .list-content, and .list-end are optional — use only the slots you need.
  • Style modifiers (is-bordered, is-hoverable, is-striped, is-numbered) apply to the .listview container.
  • State modifiers (is-active, is-disabled) and size modifiers (is-medium, is-large) apply to .list-item.
  • .is-disabled does not remove keyboard focus — always pair with aria-disabled="true" and tabindex="-1".
  • Use <a> as .list-item for fully clickable rows; the entire row becomes the tap target.
  • is-numbered uses CSS counters — no manual markup required.
  • is-circle on is-floating only takes effect when combined with is-floating.
  • Style modifiers can be combined freely — is-bordered is-hoverable is-striped is valid.

FrontAlign