List Group
Flexible and powerful lists for content display, navigation, and interactive item selection — with full keyboard navigation and accessibility support

List Group

List Group is a flexible component for rendering sequential content — from simple navigation links to rich content rows with icons, descriptive text, and trailing badges. Items can be made interactive with hover states, active selection, and full JavaScript control via E.listGroup().

Key Features

  • CSS-Only — works with plain HTML classes, no JavaScript required for static lists
  • JS APIE.listGroup() enables single- and multi-select, keyboard navigation, and callbacks
  • Colour Variants — six semantic colour modifiers (primary, secondary, success, danger, warning, info)
  • Size Variants — compact .list-group-sm and spacious .list-group-lg
  • Flush Mode.list-group-flush removes outer border for embedding inside cards
  • Rich Items — icon, heading, descriptive text, and trailing area all built-in
  • Full Accessibility — roving tabindex, ARIA roles, keyboard navigation (arrows, Home, End)

At a Glance

  • Dashboard
  • Profile New
  • Settings
  • Admin
  • Deployment complete Production — 2 minutes ago Live
  • High memory usage Server A — 87% used Warn
  • Database connection lost DB-02 — 14 seconds ago Error
<!-- CSS-only static list -->
<ul class="list-group">
    <li class="list-group-item active">Dashboard</li>
    <li class="list-group-item">Profile</li>
    <li class="list-group-item disabled">Admin</li>
</ul>

<!-- Interactive JS API -->
const lg = E.listGroup('#my-list', {
    selectable: true,
    onChange: (selected) => console.log(selected)
});

Getting Started

The simplest List Group is a <ul> with .list-group and <li> children with .list-group-item. No JavaScript needed.

  • Apples
  • Oranges
  • Bananas
  • Strawberries
<ul class="list-group">
    <li class="list-group-item">Apples</li>
    <li class="list-group-item">Oranges</li>
    <li class="list-group-item">Bananas</li>
    <li class="list-group-item">Strawberries</li>
</ul>

Add .list-group-item-action to make items interactive — cursor pointer and hover highlight:

  • Dashboard
  • Analytics
  • Reports
  • Archive
<ul class="list-group">
    <li class="list-group-item list-group-item-action active">Dashboard</li>
    <li class="list-group-item list-group-item-action">Analytics</li>
    <li class="list-group-item list-group-item-action">Reports</li>
    <li class="list-group-item list-group-item-action disabled">Archive</li>
</ul>

Basic Variants

Plain Items

The default appearance — a clean bordered list.

  • First item
  • Second item
  • Third item
  • Fourth item
<ul class="list-group">
    <li class="list-group-item">First item</li>
    <li class="list-group-item">Second item</li>
    <li class="list-group-item">Third item</li>
</ul>

With Icons

Use .list-group-item-icon for a left-aligned icon or avatar area. Pair with <span data-icon="..."> and call I.scan().

  • Inbox
  • Sent
  • Drafts
  • Bin
<ul class="list-group">
    <li class="list-group-item">
        <span class="list-group-item-icon">
            <span data-icon="mail"></span>
        </span>
        Inbox
    </li>
    <li class="list-group-item">
        <span class="list-group-item-icon">
            <span data-icon="send"></span>
        </span>
        Sent
    </li>
</ul>

With Content (Heading + Text + Trailing)

Combine .list-group-item-content, .list-group-item-heading, .list-group-item-text, and .list-group-item-trailing for rich rows.

  • Alice Brennan alice@example.com Admin
  • Bob Clarke bob@example.com Editor
  • Carol Davies carol@example.com Viewer
<li class="list-group-item list-group-item-action">
    <span class="list-group-item-icon">
        <span data-icon="user"></span>
    </span>
    <span class="list-group-item-content">
        <span class="list-group-item-heading">Alice Brennan</span>
        <span class="list-group-item-text">alice@example.com</span>
    </span>
    <span class="list-group-item-trailing">
        <span class="badge badge-success">Admin</span>
    </span>
</li>

Colour Variants

Add a semantic colour class to any .list-group-item — it applies a left border accent and a lightly tinted background.

  • Primary .list-group-item-primary
  • Secondary .list-group-item-secondary
  • Success .list-group-item-success
  • Danger .list-group-item-danger
  • Warning .list-group-item-warning
  • Info .list-group-item-info
<ul class="list-group">
    <li class="list-group-item list-group-item-primary">Primary</li>
    <li class="list-group-item list-group-item-secondary">Secondary</li>
    <li class="list-group-item list-group-item-success">Success</li>
    <li class="list-group-item list-group-item-danger">Danger</li>
    <li class="list-group-item list-group-item-warning">Warning</li>
    <li class="list-group-item list-group-item-info">Info</li>
</ul>

Size Variants

Apply .list-group-sm for a compact list or .list-group-lg for extra padding and larger text — useful in sidebars or content-heavy interfaces.

Small — .list-group-sm

  • Home
  • Products
  • About
  • Contact

Default

  • Home
  • Products
  • About
  • Contact

Large — .list-group-lg

  • Home
  • Products
  • About
  • Contact
<!-- Compact -->
<ul class="list-group list-group-sm"> ... </ul>

<!-- Default -->
<ul class="list-group"> ... </ul>

<!-- Spacious -->
<ul class="list-group list-group-lg"> ... </ul>

Flush

.list-group-flush removes the outer border and border-radius so the list blends seamlessly when embedded inside a card. A top border is added to the first item to maintain separation from card content above it.

Recent Notifications
  • New comment on your post 5 minutes ago
  • You have a new message 1 hour ago
  • Deployment succeeded 3 hours ago
<div class="card">
    <div class="card-header">Recent Notifications</div>
    <ul class="list-group list-group-flush">
        <li class="list-group-item list-group-item-action">
            <span class="list-group-item-content">
                <span class="list-group-item-heading">New comment on your post</span>
                <span class="list-group-item-text">5 minutes ago</span>
            </span>
        </li>
        ...
    </ul>
</div>

Numbered

Add .list-group-numbered to an <ol> to prepend auto-incrementing numbers to each item via CSS counters — no JavaScript required. Works with all size and colour variants.

Basic numbered

  1. Cras justo odio
  2. Dapibus ac facilisis in
  3. Morbi leo risus
  4. Porta ac consectetur ac
  5. Vestibulum at eros

Numbered with badges

  1. Inbox 14
  2. Sent 2
  3. Starred 8
  4. Spam 99+
<ol class="list-group list-group-numbered">
    <li class="list-group-item">Cras justo odio</li>
    <li class="list-group-item">Dapibus ac facilisis in</li>
    <li class="list-group-item">Morbi leo risus</li>
</ol>

<!-- With badges -->
<ol class="list-group list-group-numbered">
    <li class="list-group-item">
        Inbox
        <span class="list-group-item-trailing"><span class="badge badge-primary">14</span></span>
    </li>
</ol>

Horizontal

Change the layout from vertical to horizontal with .list-group-horizontal. Use the responsive variants — .list-group-horizontal-sm, -md, -lg, -xl — to switch to horizontal only above a given breakpoint.

Always horizontal

  • An item
  • Active
  • A third item
  • A fourth item

Horizontal above md (768px), stacked below

  • One
  • Two
  • Three

Horizontal with action items

<!-- Always horizontal -->
<ul class="list-group list-group-horizontal">
    <li class="list-group-item">An item</li>
    <li class="list-group-item active">Active</li>
    <li class="list-group-item">A third item</li>
</ul>

<!-- Horizontal above md breakpoint (768px) -->
<ul class="list-group list-group-horizontal-md">
    <li class="list-group-item">One</li>
    <li class="list-group-item">Two</li>
    <li class="list-group-item">Three</li>
</ul>

<!-- Available breakpoints: -sm (576px), -md (768px), -lg (992px), -xl (1200px) -->

Interactive — JavaScript API

Use E.listGroup(selector, options) to enable selectable behaviour, keyboard navigation, and callbacks. Set selectable: true and add .list-group-item-action to items.

Single Select

Clicking an item selects it and deselects any previously selected item. The onChange callback receives an array of the currently selected DOM elements.

  • Overview
  • Analytics
  • Reports
  • Billing (disabled)

Selected item:

None selected
const lg = E.listGroup('#my-list', {
    selectable: true,
    onChange: (selected) => {
        const label = selected[0]
            ? selected[0].querySelector('.list-group-item-heading').textContent
            : 'None selected';
        $('#output').text(label);
    }
});

// Programmatic selection
lg.select(1);         // Select item at index 1
lg.deselect(1);       // Deselect item at index 1
lg.deselectAll();     // Clear all selections
lg.getSelected();     // → [HTMLElement, ...]

Multi-Select

Set multiSelect: true alongside selectable: true to allow multiple items to be active simultaneously. The list container receives aria-multiselectable="true" automatically.

  • Design
  • Development
  • Project Management
  • Analytics
  • HR & Hiring

Selected categories:

None selected
const lg = E.listGroup('#my-list', {
    selectable: true,
    multiSelect: true,    // Allow multiple active items
    onChange: (selected) => {
        const labels = selected.map(el =>
            el.querySelector('.list-group-item-heading').textContent
        );
        console.log('Selected:', labels);
    }
});

lg.selectAll();     // Select every non-disabled item
lg.deselectAll();   // Clear all selections
lg.toggle(2);       // Toggle item at index 2

Tutorial — Settings Panel

Build a settings panel where the left-hand List Group acts as a navigation menu and the right panel updates to show the relevant settings on each selection.

Step 1: Create the HTML structure

<div class="settings-layout">
    <ul id="settings-nav" class="list-group list-group-sm settings-sidebar">
        <li class="list-group-item list-group-item-action active">
            <span class="list-group-item-icon"><span data-icon="settings"></span></span>
            <span class="list-group-item-heading">General</span>
        </li>
        <li class="list-group-item list-group-item-action">
            <span class="list-group-item-icon"><span data-icon="lock"></span></span>
            <span class="list-group-item-heading">Security</span>
        </li>
        ...
    </ul>
    <div id="settings-panel" class="settings-panel"></div>
</div>

Step 2: Initialise with onChange callback

const panels = {
    0: { title: 'General', body: 'Manage your language, timezone, and display preferences.' },
    1: { title: 'Security', body: 'Update your password and configure two-factor authentication.' },
    2: { title: 'Notifications', body: 'Choose how and when you receive alerts.' },
    3: { title: 'Privacy', body: 'Control data sharing and visibility settings.' },
};

const settingsNav = E.listGroup('#settings-nav', {
    selectable: true,
    onChange: (selected) => {
        const index = lg.getItems().indexOf(selected[0]);
        const p = panels[index];
        $('#settings-panel').html(
            `<h4>${p.title}</h4><p>${p.body}</p>`
        );
    }
});

Step 3: Live Demo

  • General
  • Security
  • Notifications
  • Privacy

General

Manage your language, timezone, and display preferences.

JavaScript API

Initialisation

const lg = E.listGroup('#my-list', {
    selectable: true,         // Enable click-to-select behaviour
    multiSelect: false,       // Allow multiple active items (default: false)
    activeClass: 'active',    // CSS class for selected state (default: 'active')
    disabledClass: 'disabled',// CSS class for disabled items (default: 'disabled')
    itemSelector: '.list-group-item', // Item query selector (default: '.list-group-item')
    keyboard: true,           // Enable arrow-key navigation (default: true)
    loop: true,               // Wrap focus at list boundaries (default: true)
    focusOnInit: false,       // Focus first enabled item on init (default: false)
    onChange:  (selected, event) => {},  // Fires on selection change
    onSelect:  (item, index, event) => {},  // Fires when item selected
    onDeselect:(item, index, event) => {},  // Fires when item deselected
});

Selection Methods

lg.select(index)       // Select item at index (fires callbacks)
lg.deselect(index)     // Deselect item at index (fires callbacks)
lg.toggle(index)       // Toggle selection at index
lg.selectAll()         // Select all non-disabled items (multiSelect mode only)
lg.deselectAll()       // Deselect all items
lg.getSelected()       // → HTMLElement[] of currently active items
lg.isSelected(index)   // → boolean

Item Methods

lg.getItems()          // → HTMLElement[] — all items
lg.getItem(index)      // → HTMLElement | null
lg.enable(index)       // Remove disabled class and aria-disabled
lg.disable(index)      // Add disabled class and aria-disabled
lg.refresh()           // Re-query items from DOM (use after dynamic changes)
lg.destroy()           // Remove event listeners and clean up ARIA attributes

Keyboard Navigation

When keyboard navigation is enabled (default), users can move between items without using the mouse. The component uses a roving tabindex pattern — only the focused item is in the tab order at any time.

KeyAction
TabMove focus into (or away from) the list
/ Move focus to the next enabled item
/ Move focus to the previous enabled item
HomeMove focus to the first enabled item
EndMove focus to the last enabled item
Enter / SpaceSelect (or toggle) the focused item (when selectable: true)
// Disable keyboard navigation
const lg = E.listGroup('#my-list', {
    selectable: true,
    keyboard: false   // Turn off arrow-key navigation
});

// Disable looping at boundaries
const lg = E.listGroup('#my-list', {
    selectable: true,
    loop: false  // Focus stops at first / last item
});

Accessibility

Automatic ARIA Attributes

When selectable: true is set, the JS API manages ARIA attributes automatically — no manual aria-* attributes needed in your HTML.

ElementAttributeValue
.list-grouprole"listbox"
.list-group (multi)aria-multiselectable"true"
.list-group-itemaria-selected"true" or "false"
.list-group-item.disabledaria-disabled"true"
.list-group-itemtabindexRoving: 0 (focused) or -1

CSS-Only Guidance

For CSS-only (non-JS) lists, add ARIA attributes yourself when semantics matter:

<!-- Navigation list — use <nav> + <ul> -->
<nav aria-label="Main navigation">
    <ul class="list-group">
        <li class="list-group-item">
            <a href="/dashboard" aria-current="page">Dashboard</a>
        </li>
        <li class="list-group-item"><a href="/reports">Reports</a></li>
    </ul>
</nav>

<!-- Content list — plain <ul> is fine -->
<ul class="list-group" aria-label="Notifications">
    <li class="list-group-item">...</li>
</ul>

Screen Reader Notes

  • In selectable mode the list is announced as a listbox; each item as an option.
  • Selected state is announced via aria-selected, so avoid relying on colour alone.
  • Icon-only list items should include visually-hidden text or an aria-label on the item for context.
  • Disabled items receive aria-disabled="true" and pointer-events: none, so keyboard focus skips them automatically.

Config Engine

List Group is supported in the declarative config engine via $.setup(), allowing you to initialise the component without writing imperative JavaScript.

$.setup({
    '#my-list': {
        component: 'listGroup',
        options: {
            selectable: true,
            multiSelect: false,
            onChange: (selected) => {
                console.log('Selection changed:', selected.length, 'item(s) selected');
            }
        }
    }
});

Live Config Engine Demo

  • Initialised via Config Engine
  • Click to select an item
  • Works exactly like JS API
No selection

CSS Customisation

List Group uses standard Domma design tokens. Override these variables globally or scoped to a specific container.

VariableControls
--dm-borderOuter border colour of the list group
--dm-border-lightSeparator colour between items
--dm-surfaceItem background colour
--dm-background-altItem hover background colour
--dm-border-focusFocus ring colour (inset box-shadow)
--dm-primaryActive item background colour
--dm-radius-mdBorder radius of the list group container
--dm-space-3 / --dm-space-4Default item padding
/* Tighter separators and softer border */
#my-list {
    --dm-border: rgba(0, 0, 0, 0.08);
    --dm-border-light: rgba(0, 0, 0, 0.04);
}

/* Custom active colour (override primary) */
#my-list .list-group-item.active {
    background-color: #16a34a;   /* Green */
    border-bottom-color: #15803d;
}

/* Borderless items with rounded container */
#my-list {
    --dm-radius-md: 1rem;
}
#my-list .list-group-item {
    border-bottom: none;
    margin-bottom: 2px;
}

Full CSS Customisation Cheat-Sheet →

API Reference

Options

OptionTypeDefaultDescription
selectablebooleanfalseEnable click-to-select behaviour and ARIA listbox roles
multiSelectbooleanfalseAllow more than one item to be active at a time
activeClassstring'active'CSS class applied to selected items
disabledClassstring'disabled'CSS class that marks items as non-interactive
itemSelectorstring'.list-group-item'Query selector used to discover child items
keyboardbooleantrueEnable arrow-key / Home / End navigation
loopbooleantrueWrap focus when navigating past first / last item
focusOnInitbooleanfalseAutomatically focus the first enabled item on initialisation
onChangefunctionnull(selected: HTMLElement[], event) => void — fires on any selection change
onSelectfunctionnull(item: HTMLElement, index: number, event) => void — fires when an item is selected
onDeselectfunctionnull(item: HTMLElement, index: number, event) => void — fires when an item is deselected

Methods

MethodReturnsDescription
select(index)thisSelect item at index; fires onSelect and onChange
deselect(index)thisDeselect item at index; fires onDeselect and onChange
toggle(index)thisToggle selection at index
selectAll()thisSelect all non-disabled items (multiSelect mode only)
deselectAll()thisDeselect all items; fires callbacks
getSelected()HTMLElement[]Returns array of currently active items
isSelected(index)booleanWhether item at index is currently selected
getItems()HTMLElement[]Returns all item elements as a shallow copy
getItem(index)HTMLElement|nullReturns single item element at index
enable(index)thisRemove disabled class and aria-disabled from item
disable(index)thisAdd disabled class and aria-disabled to item
refresh()thisRe-query items from DOM; call after dynamic content changes
destroy()voidRemove event listeners and clean up all ARIA attributes

CSS Classes

ClassDescription
.list-groupContainer — flex column, rounded border, overflow hidden
.list-group-flushModifier — remove outer border and radius (for card embedding)
.list-group-smModifier — reduced padding and smaller font size
.list-group-lgModifier — increased padding and base font size
.list-group-itemItem — flex row, vertically centred, bottom separator
.list-group-item-actionModifier — pointer cursor and hover highlight
.list-group-item.activeSelected/active state — primary background, white text
.list-group-item.disabledNon-interactive state — dimmed, no pointer events
.list-group-item-iconLeft-aligned icon/avatar slot
.list-group-item-contentFlex-grow text wrapper
.list-group-item-headingPrimary text — semi-bold, truncated with ellipsis
.list-group-item-textSecondary/muted text — smaller, truncated
.list-group-item-trailingRight-aligned area (badges, timestamps, icons)
.list-group-item-primaryColour variant — left border + tinted background
.list-group-item-secondaryColour variant
.list-group-item-successColour variant
.list-group-item-dangerColour variant
.list-group-item-warningColour variant
.list-group-item-infoColour variant

Related Elements