View as Markdown

List Group

Display a flexible series of content with active, disabled, actionable, and contextual states.

100%
Loading...
<twig:ListGroup tag="div" style="max-width: 28rem;">
    <twig:ListGroup:Item href="#" active>The current link item</twig:ListGroup:Item>
    <twig:ListGroup:Item href="#">A second link item</twig:ListGroup:Item>
    <twig:ListGroup:Item href="#">A third link item</twig:ListGroup:Item>
    <twig:ListGroup:Item href="#" disabled>A disabled link item</twig:ListGroup:Item>
</twig:ListGroup>

Installation

php bin/console ux:install list-group --kit bootstrap

Install the following Composer dependencies:

composer require twig/extra-bundle twig/html-extra:^3.12.0

Install the following front-end dependencies:

npm install bootstrap@^5.3.0
# ...or with Symfony AssetMapper
php bin/console importmap:require bootstrap bootstrap/dist/css/bootstrap.min.css

Copy the following file(s) into your app:

{# @prop tag &#039;ul&#039;|&#039;ol&#039;|&#039;div&#039;|null The HTML element to render. #}
{# @prop flush boolean Whether to remove outer borders and rounded corners. #}
{# @prop numbered boolean Whether to display CSS-generated item numbers. #}
{# @prop horizontal boolean|&#039;sm&#039;|&#039;md&#039;|&#039;lg&#039;|&#039;xl&#039;|&#039;xxl&#039; Whether and when to use a horizontal layout. #}
{# @block content The list group items. #}
{%- props
    tag = null,
    flush = false,
    numbered = false,
    horizontal = false
-%}

{%- set final_tag = tag|lower in [&#039;ul&#039;, &#039;ol&#039;, &#039;div&#039;] ? tag|lower : (numbered ? &#039;ol&#039; : &#039;ul&#039;) -%}
{%- set classes = html_classes({
    &#039;list-group&#039;: true,
    &#039;list-group-flush&#039;: flush,
    &#039;list-group-numbered&#039;: numbered,
    &#039;list-group-horizontal&#039;: horizontal is same as(true),
    (&#039;list-group-horizontal-&#039; ~ horizontal): horizontal is not same as(true) and horizontal is not same as(false) and horizontal in [&#039;sm&#039;, &#039;md&#039;, &#039;lg&#039;, &#039;xl&#039;, &#039;xxl&#039;],
}) -%}

&lt;{{ final_tag }} {{ attributes.defaults({class: classes}) }}&gt;
    {%- block content -%}{%- endblock -%}
&lt;/{{ final_tag }}&gt;
{# @prop tag &#039;li&#039;|&#039;a&#039;|&#039;button&#039;|null The HTML element to render. #}
{# @prop href string|null The destination used for anchor items. #}
{# @prop type &#039;button&#039;|&#039;submit&#039;|&#039;reset&#039; The type used for button items. #}
{# @prop active boolean Whether the item represents the current selection. #}
{# @prop current boolean|string|null Whether and how to expose an active item as current. #}
{# @prop disabled boolean Whether the actionable item is disabled. #}
{# @prop color &#039;primary&#039;|&#039;secondary&#039;|&#039;success&#039;|&#039;danger&#039;|&#039;warning&#039;|&#039;info&#039;|&#039;light&#039;|&#039;dark&#039;|null The Bootstrap contextual color. #}
{# @prop label string The fallback label when no content block is provided. #}
{# @block content The item content. #}
{%- props
    tag = null,
    href = null,
    type = &#039;button&#039;,
    active = false,
    current = null,
    disabled = false,
    color = null,
    label = &#039;&#039;
-%}

{%- set final_tag = tag|lower in [&#039;li&#039;, &#039;a&#039;, &#039;button&#039;] ? tag|lower : (href is not null ? &#039;a&#039; : &#039;li&#039;) -%}
{%- set actionable = final_tag in [&#039;a&#039;, &#039;button&#039;] -%}
{%- set classes = html_classes({
    &#039;list-group-item&#039;: true,
    &#039;list-group-item-action&#039;: actionable,
    active: active,
    disabled: disabled and final_tag == &#039;a&#039;,
    (&#039;list-group-item-&#039; ~ color): color in [&#039;primary&#039;, &#039;secondary&#039;, &#039;success&#039;, &#039;danger&#039;, &#039;warning&#039;, &#039;info&#039;, &#039;light&#039;, &#039;dark&#039;],
}) -%}
{%- set default_attrs = {class: classes} -%}

{%- if active and current is not same as(false) -%}
    {%- set default_attrs = default_attrs|merge({&#039;aria-current&#039;: current ?? &#039;true&#039;}) -%}
{%- endif -%}

{%- if final_tag == &#039;a&#039; -%}
    {%- if disabled -%}
        {%- set default_attrs = default_attrs|merge({&#039;aria-disabled&#039;: &#039;true&#039;, tabindex: &#039;-1&#039;}) -%}
    {%- else -%}
        {%- set default_attrs = default_attrs|merge({href: href ?? &#039;#&#039;}) -%}
    {%- endif -%}
{%- elseif final_tag == &#039;button&#039; -%}
    {%- set default_attrs = default_attrs|merge({type: type}) -%}
    {%- if disabled -%}
        {%- set default_attrs = default_attrs|merge({disabled: true}) -%}
    {%- endif -%}
{%- endif -%}

&lt;{{ final_tag }} {{ attributes.defaults(default_attrs) }}&gt;
    {%- block content -%}
        {{- label -}}
    {%- endblock -%}
&lt;/{{ final_tag }}&gt;

Usage

<twig:ListGroup>
    <twig:ListGroup:Item>An item</twig:ListGroup:Item>
    <twig:ListGroup:Item>A second item</twig:ListGroup:Item>
    <twig:ListGroup:Item active>A third item</twig:ListGroup:Item>
</twig:ListGroup>

Accessibility

Do not rely on contextual color alone to communicate meaning. Make the meaning explicit in the visible content or provide additional visually hidden text.

Disabled links omit their href, expose aria-disabled="true", and are removed from keyboard navigation. Associate every checkbox and radio with a visible label or an aria-label.

Examples

Basic example

Use a list group to display a simple series of related items.

100%
Loading...
<twig:ListGroup>
    <twig:ListGroup:Item>An item</twig:ListGroup:Item>
    <twig:ListGroup:Item>A second item</twig:ListGroup:Item>
    <twig:ListGroup:Item>A third item</twig:ListGroup:Item>
    <twig:ListGroup:Item>A fourth item</twig:ListGroup:Item>
    <twig:ListGroup:Item>And a fifth one</twig:ListGroup:Item>
</twig:ListGroup>

Active items

Mark the current selection with the active state.

100%
Loading...
<twig:ListGroup>
    <twig:ListGroup:Item active>An active item</twig:ListGroup:Item>
    <twig:ListGroup:Item>A second item</twig:ListGroup:Item>
    <twig:ListGroup:Item>A third item</twig:ListGroup:Item>
    <twig:ListGroup:Item>A fourth item</twig:ListGroup:Item>
    <twig:ListGroup:Item>And a fifth one</twig:ListGroup:Item>
</twig:ListGroup>

Render actionable list items as links or buttons to expose hover, active, and disabled states.

100%
Loading...
<div class="vstack gap-3">
    <twig:ListGroup tag="div">
        <twig:ListGroup:Item href="#" active>The current link item</twig:ListGroup:Item>
        <twig:ListGroup:Item href="#">A second link item</twig:ListGroup:Item>
        <twig:ListGroup:Item href="#">A third link item</twig:ListGroup:Item>
        <twig:ListGroup:Item href="#">A fourth link item</twig:ListGroup:Item>
        <twig:ListGroup:Item href="#" disabled>A disabled link item</twig:ListGroup:Item>
    </twig:ListGroup>

    <twig:ListGroup tag="div">
        <twig:ListGroup:Item tag="button" active>The current button</twig:ListGroup:Item>
        <twig:ListGroup:Item tag="button">A second button item</twig:ListGroup:Item>
        <twig:ListGroup:Item tag="button">A third button item</twig:ListGroup:Item>
        <twig:ListGroup:Item tag="button">A fourth button item</twig:ListGroup:Item>
        <twig:ListGroup:Item tag="button" disabled>A disabled button item</twig:ListGroup:Item>
    </twig:ListGroup>
</div>

Flush

Remove outer borders and rounded corners for edge-to-edge rendering inside a parent container.

100%
Loading...
<twig:ListGroup flush>
    <twig:ListGroup:Item>An item</twig:ListGroup:Item>
    <twig:ListGroup:Item>A second item</twig:ListGroup:Item>
    <twig:ListGroup:Item>A third item</twig:ListGroup:Item>
    <twig:ListGroup:Item>A fourth item</twig:ListGroup:Item>
    <twig:ListGroup:Item>And a fifth one</twig:ListGroup:Item>
</twig:ListGroup>

Numbered

Generate item numbers with Bootstrap's CSS counters, including for items with custom content.

100%
Loading...
<div class="vstack gap-3">
    <twig:ListGroup numbered>
        <twig:ListGroup:Item>A list item</twig:ListGroup:Item>
        <twig:ListGroup:Item>A list item</twig:ListGroup:Item>
        <twig:ListGroup:Item>A list item</twig:ListGroup:Item>
    </twig:ListGroup>

    <twig:ListGroup numbered>
        {% for item in 1..3 %}
            <twig:ListGroup:Item class="d-flex justify-content-between align-items-start">
                <div class="ms-2 me-auto">
                    <div class="fw-bold">Subheading</div>
                    Content for list item
                </div>
                <span class="badge text-bg-primary rounded-pill">14</span>
            </twig:ListGroup:Item>
        {% endfor %}
    </twig:ListGroup>
</div>

Horizontal

Display items horizontally at every breakpoint or from a selected responsive breakpoint.

100%
Loading...
<div class="vstack gap-2">
    {% for breakpoint in [true, 'sm', 'md', 'lg', 'xl', 'xxl'] %}
        <twig:ListGroup :horizontal="breakpoint">
            <twig:ListGroup:Item>An item</twig:ListGroup:Item>
            <twig:ListGroup:Item>A second item</twig:ListGroup:Item>
            <twig:ListGroup:Item>A third item</twig:ListGroup:Item>
        </twig:ListGroup>
    {% endfor %}
</div>

Variants

Apply contextual colors to non-actionable list items.

100%
Loading...
<twig:ListGroup>
    <twig:ListGroup:Item>A simple default list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item color="primary">A simple primary list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item color="secondary">A simple secondary list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item color="success">A simple success list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item color="danger">A simple danger list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item color="warning">A simple warning list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item color="info">A simple info list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item color="light">A simple light list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item color="dark">A simple dark list group item</twig:ListGroup:Item>
</twig:ListGroup>

Action variants

Combine contextual colors with actionable links and their hover and active states.

100%
Loading...
<twig:ListGroup tag="div">
    <twig:ListGroup:Item href="#">A simple default list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item href="#" color="primary">A simple primary list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item href="#" color="secondary">A simple secondary list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item href="#" color="success">A simple success list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item href="#" color="danger">A simple danger list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item href="#" color="warning">A simple warning list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item href="#" color="info">A simple info list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item href="#" color="light">A simple light list group item</twig:ListGroup:Item>
    <twig:ListGroup:Item href="#" color="dark">A simple dark list group item</twig:ListGroup:Item>
</twig:ListGroup>

With badges

Add badges to communicate counts or activity alongside an item label.

100%
Loading...
<twig:ListGroup>
    <twig:ListGroup:Item class="d-flex justify-content-between align-items-center">
        A list item
        <span class="badge text-bg-primary rounded-pill">14</span>
    </twig:ListGroup:Item>
    <twig:ListGroup:Item class="d-flex justify-content-between align-items-center">
        A second list item
        <span class="badge text-bg-primary rounded-pill">2</span>
    </twig:ListGroup:Item>
    <twig:ListGroup:Item class="d-flex justify-content-between align-items-center">
        A third list item
        <span class="badge text-bg-primary rounded-pill">1</span>
    </twig:ListGroup:Item>
</twig:ListGroup>

Custom content

Compose headings, supporting text, metadata, and other HTML within actionable items.

100%
Loading...
<twig:ListGroup tag="div">
    <twig:ListGroup:Item href="#" active>
        <div class="d-flex w-100 justify-content-between">
            <h5 class="mb-1">List group item heading</h5>
            <small>3 days ago</small>
        </div>
        <p class="mb-1">Some placeholder content in a paragraph.</p>
        <small>And some small print.</small>
    </twig:ListGroup:Item>
    {% for item in 1..2 %}
        <twig:ListGroup:Item href="#">
            <div class="d-flex w-100 justify-content-between">
                <h5 class="mb-1">List group item heading</h5>
                <small class="text-body-secondary">3 days ago</small>
            </div>
            <p class="mb-1">Some placeholder content in a paragraph.</p>
            <small class="text-body-secondary">And some muted small print.</small>
        </twig:ListGroup:Item>
    {% endfor %}
</twig:ListGroup>

Checkboxes and radios

Place labeled form controls inside list items, with an optional stretched label for a larger click target.

100%
Loading...
<div class="vstack gap-3">
    <twig:ListGroup>
        <twig:ListGroup:Item>
            <input class="form-check-input me-1" type="checkbox" id="firstCheckbox">
            <label class="form-check-label" for="firstCheckbox">First checkbox</label>
        </twig:ListGroup:Item>
        <twig:ListGroup:Item>
            <input class="form-check-input me-1" type="checkbox" id="secondCheckbox">
            <label class="form-check-label" for="secondCheckbox">Second checkbox</label>
        </twig:ListGroup:Item>
        <twig:ListGroup:Item>
            <input class="form-check-input me-1" type="checkbox" id="thirdCheckbox">
            <label class="form-check-label" for="thirdCheckbox">Third checkbox</label>
        </twig:ListGroup:Item>
    </twig:ListGroup>

    <twig:ListGroup>
        <twig:ListGroup:Item>
            <input class="form-check-input me-1" type="radio" name="listGroupRadio" id="firstRadio" checked>
            <label class="form-check-label" for="firstRadio">First radio</label>
        </twig:ListGroup:Item>
        <twig:ListGroup:Item>
            <input class="form-check-input me-1" type="radio" name="listGroupRadio" id="secondRadio">
            <label class="form-check-label" for="secondRadio">Second radio</label>
        </twig:ListGroup:Item>
        <twig:ListGroup:Item>
            <input class="form-check-input me-1" type="radio" name="listGroupRadio" id="thirdRadio">
            <label class="form-check-label" for="thirdRadio">Third radio</label>
        </twig:ListGroup:Item>
    </twig:ListGroup>

    <twig:ListGroup>
        <twig:ListGroup:Item>
            <input class="form-check-input me-1" type="checkbox" id="firstCheckboxStretched">
            <label class="form-check-label stretched-link" for="firstCheckboxStretched">First checkbox</label>
        </twig:ListGroup:Item>
        <twig:ListGroup:Item>
            <input class="form-check-input me-1" type="checkbox" id="secondCheckboxStretched">
            <label class="form-check-label stretched-link" for="secondCheckboxStretched">Second checkbox</label>
        </twig:ListGroup:Item>
        <twig:ListGroup:Item>
            <input class="form-check-input me-1" type="checkbox" id="thirdCheckboxStretched">
            <label class="form-check-label stretched-link" for="thirdCheckboxStretched">Third checkbox</label>
        </twig:ListGroup:Item>
    </twig:ListGroup>
</div>

JavaScript behavior

Use Bootstrap's tab plugin and data attributes to connect list items with tab panels.

100%
Loading...
<div class="row">
    <div class="col-4">
        <twig:ListGroup tag="div" id="list-tab" role="tablist">
            <twig:ListGroup:Item
                href="#list-home"
                id="list-home-list"
                role="tab"
                data-bs-toggle="list"
                aria-controls="list-home"
                :current="false"
                active
            >Home</twig:ListGroup:Item>
            <twig:ListGroup:Item
                href="#list-profile"
                id="list-profile-list"
                role="tab"
                data-bs-toggle="list"
                aria-controls="list-profile"
            >Profile</twig:ListGroup:Item>
            <twig:ListGroup:Item
                href="#list-messages"
                id="list-messages-list"
                role="tab"
                data-bs-toggle="list"
                aria-controls="list-messages"
            >Messages</twig:ListGroup:Item>
            <twig:ListGroup:Item
                href="#list-settings"
                id="list-settings-list"
                role="tab"
                data-bs-toggle="list"
                aria-controls="list-settings"
            >Settings</twig:ListGroup:Item>
        </twig:ListGroup>
    </div>
    <div class="col-8">
        <div class="tab-content" id="list-tab-content">
            <div class="tab-pane fade show active" id="list-home" role="tabpanel" aria-labelledby="list-home-list" tabindex="0">
                Some placeholder content relating to Home.
            </div>
            <div class="tab-pane fade" id="list-profile" role="tabpanel" aria-labelledby="list-profile-list" tabindex="0">
                Some placeholder content relating to Profile.
            </div>
            <div class="tab-pane fade" id="list-messages" role="tabpanel" aria-labelledby="list-messages-list" tabindex="0">
                Some placeholder content relating to Messages.
            </div>
            <div class="tab-pane fade" id="list-settings" role="tabpanel" aria-labelledby="list-settings-list" tabindex="0">
                Some placeholder content relating to Settings.
            </div>
        </div>
    </div>
</div>

API Reference

<twig:ListGroup>

Prop Type Default
tag 
'ul'|'ol'|'div'|null null
flush 
boolean false
numbered 
boolean false
horizontal 
boolean|'sm'|'md'|'lg'|'xl'|'xxl' false
Block Description
content The list group items.

<twig:ListGroup:Item>

Prop Type Default
tag 
'li'|'a'|'button'|null null
href 
string|null null
type 
'button'|'submit'|'reset' 'button'
active 
boolean false
current 
boolean|string|null null
disabled 
boolean false
color 
'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark'|null null
label 
string ''
Block Description
content The item content.