Button Group

A container that groups related buttons together with consistent styling.

Loading...
<twig:ButtonGroup>
    <twig:ButtonGroup class="hidden sm:flex">
        <twig:Button variant="outline" size="icon" aria-label="Go back">
            <twig:ux:icon name="lucide:arrow-left" />
        </twig:Button>
    </twig:ButtonGroup>
    <twig:ButtonGroup>
        <twig:Button variant="outline">Archive</twig:Button>
        <twig:Button variant="outline">Report</twig:Button>
    </twig:ButtonGroup>
    <twig:ButtonGroup>
        <twig:Button variant="outline">
            <twig:ux:icon name="lucide:clock" />
            Snooze
        </twig:Button>
        <twig:Button variant="outline" size="icon" aria-label="More options">
            <twig:ux:icon name="lucide:more-horizontal" />
        </twig:Button>
    </twig:ButtonGroup>
</twig:ButtonGroup>

Installation

bin/console ux:install button-group --kit shadcn

That's it!

Install the following Composer dependencies:

composer require twig/extra-bundle twig/html-extra:^3.12.0 tales-from-a-dev/twig-tailwind-extra:^1.0.0

Copy the following file(s) into your Symfony app:

templates/components/ButtonGroup.html.twig
{# @prop orientation 'horizontal'|'vertical' The layout direction of the button group. Defaults to `horizontal` #}
{# @block content The grouped buttons and/or separators #}
{%- props orientation = 'horizontal' -%}
{%- set style = html_cva(
    base: 'group/button-group flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 ltr:has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg rtl:has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-e-lg [&>[data-slot=select-trigger]:not([class*=\'w-\'])]:w-fit [&>input]:flex-1 ',
    variants: {
        orientation: {
            horizontal: 'ltr:[&>*:not(:first-child)]:rounded-l-none rtl:[&>*:not(:first-child)]:rounded-s-none ltr:[&>*:not(:first-child)]:border-l-0 rtl:[&>*:not(:first-child)]:border-s-0 ltr:[&>*:not(:last-child)]:rounded-r-none rtl:[&>*:not(:last-child)]:rounded-e-none ltr:[&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-lg! rtl:[&>[data-slot]:not(:has(~[data-slot]))]:rounded-e-lg!',
            vertical: 'flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-lg!',
        },
    },
    default_variant: {
        orientation: 'horizontal',
    },
) -%}

<div
    role="group"
    data-slot="button-group"
    data-orientation="{{ orientation }}"
    class="{{ style.apply({orientation: orientation}, attributes.render('class'))|tailwind_merge }}"
    {{ attributes }}
>
    {%- block content %}{% endblock -%}
</div>
templates/components/ButtonGroup/Separator.html.twig
{# @prop orientation 'horizontal'|'vertical' The separator orientation. Defaults to `vertical` #}
{# @block content Optional custom separator content #}
{%- props orientation = 'vertical' -%}
<twig:Separator
    orientation="{{ orientation }}"
    data-slot="button-group-separator"
    class="{{ ('relative self-stretch bg-input ' ~ (orientation == 'vertical' ? 'my-px h-auto' : 'mx-px w-auto') ~ ' ' ~ attributes.render('class'))|tailwind_merge }}"
    {{ ...attributes }}
/>
templates/components/ButtonGroup/Text.html.twig
{# @prop as 'div' The HTML tag to render. Defaults to `div` #}
{# @block content The text content displayed in the button group #}
{%- props as = 'div' -%}
<{{ as }}
    data-slot="button-group-text"
    class="{{ ('flex items-center gap-2 rounded-lg border bg-muted px-2.5 text-sm font-medium [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4 ' ~ attributes.render('class'))|tailwind_merge }}"
    {{ attributes }}
>
    {%- block content %}{% endblock -%}
</{{ as }}>
templates/components/Separator.html.twig
{# @prop orientation 'horizontal'|'vertical' The separator orientation. Defaults to `horizontal` #}
{# @prop decorative boolean Whether the separator is purely decorative (not semantic). Defaults to `true` #}
{%- props orientation = 'horizontal', decorative = true -%}
<div
    class="{{ ('shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch ' ~ attributes.render('class'))|tailwind_merge }}"
    {{ attributes.defaults({
        'data-slot': 'separator',
        'data-orientation': orientation,
        role: decorative ? 'none' : 'separator',
        'aria-orientation': decorative ? false : orientation,
    }) }}
></div>

Happy coding!

Usage

<twig:ButtonGroup>
    <twig:Button>Button 1</twig:Button>
    <twig:Button>Button 2</twig:Button>
</twig:ButtonGroup>

Accessibility

  • The ButtonGroup component has the role attribute set to group.
  • Use Tab to navigate between the buttons in the group.
  • Use aria-label or aria-labelledby to label the button group.

Examples

Orientation

Set the orientation prop to change the button group layout.

Loading...
<twig:ButtonGroup orientation="vertical" aria-label="Media controls" class="h-fit">
    <twig:Button variant="outline" size="icon">
        <twig:ux:icon name="lucide:plus" />
    </twig:Button>
    <twig:Button variant="outline" size="icon">
        <twig:ux:icon name="lucide:minus" />
    </twig:Button>
</twig:ButtonGroup>

Size

Control the size of buttons using the size prop on individual buttons.

Loading...
<div class="flex flex-col items-start gap-8">
    <twig:ButtonGroup>
        <twig:Button variant="outline" size="sm">Small</twig:Button>
        <twig:Button variant="outline" size="sm">Button</twig:Button>
        <twig:Button variant="outline" size="sm">Group</twig:Button>
        <twig:Button variant="outline" size="icon-sm">
            <twig:ux:icon name="lucide:plus" />
        </twig:Button>
    </twig:ButtonGroup>
    <twig:ButtonGroup>
        <twig:Button variant="outline">Default</twig:Button>
        <twig:Button variant="outline">Button</twig:Button>
        <twig:Button variant="outline">Group</twig:Button>
        <twig:Button variant="outline" size="icon">
            <twig:ux:icon name="lucide:plus" />
        </twig:Button>
    </twig:ButtonGroup>
    <twig:ButtonGroup>
        <twig:Button variant="outline" size="lg">Large</twig:Button>
        <twig:Button variant="outline" size="lg">Button</twig:Button>
        <twig:Button variant="outline" size="lg">Group</twig:Button>
        <twig:Button variant="outline" size="icon-lg">
            <twig:ux:icon name="lucide:plus" />
        </twig:Button>
    </twig:ButtonGroup>
</div>

Nested

Nest ButtonGroup components to create button groups with spacing.

Loading...
<twig:ButtonGroup>
    <twig:ButtonGroup>
        <twig:Button variant="outline" size="icon">
            <twig:ux:icon name="lucide:plus" />
        </twig:Button>
    </twig:ButtonGroup>
    <twig:ButtonGroup>
        <twig:InputGroup>
            <twig:InputGroup:Input placeholder="Send a message..." />
            <twig:InputGroup:Addon align="inline-end">
                <twig:ux:icon name="lucide:audio-lines" />
            </twig:InputGroup:Addon>
        </twig:InputGroup>
    </twig:ButtonGroup>
</twig:ButtonGroup>

Separator

The ButtonGroup:Separator component visually divides buttons within a group.

Buttons with variant outline do not need a separator since they have a border. For other variants, a separator is recommended to improve the visual hierarchy.

Loading...
<twig:ButtonGroup>
    <twig:Button variant="secondary" size="sm">Copy</twig:Button>
    <twig:ButtonGroup:Separator />
    <twig:Button variant="secondary" size="sm">Paste</twig:Button>
</twig:ButtonGroup>

Split

Create a split button group by adding two buttons separated by a ButtonGroup:Separator.

Loading...
<twig:ButtonGroup>
    <twig:Button variant="secondary">Button</twig:Button>
    <twig:ButtonGroup:Separator />
    <twig:Button size="icon" variant="secondary">
        <twig:ux:icon name="tabler:plus" />
    </twig:Button>
</twig:ButtonGroup>

Input

Wrap an Input component with buttons.

Loading...
<twig:ButtonGroup>
    <twig:Input placeholder="Search..." />
    <twig:Button variant="outline" aria-label="Search">
        <twig:ux:icon name="lucide:search" />
    </twig:Button>
</twig:ButtonGroup>

Input Group

Wrap an InputGroup component to create complex input layouts.

Loading...
<twig:ButtonGroup class="[--radius:9999rem]">
    <twig:ButtonGroup>
        <twig:Button variant="outline" size="icon">
            <twig:ux:icon name="lucide:plus" />
        </twig:Button>
    </twig:ButtonGroup>
    <twig:ButtonGroup>
        <twig:InputGroup>
            <twig:InputGroup:Input placeholder="Send a message..." />
            <twig:InputGroup:Addon align="inline-end">
                <twig:ux:icon name="lucide:audio-lines" />
            </twig:InputGroup:Addon>
        </twig:InputGroup>
    </twig:ButtonGroup>
</twig:ButtonGroup>

RTL

To enable RTL support, set the dir="rtl" attribute on the root element.

Loading...
<div class="flex flex-col gap-6">
    <div dir="rtl">
        <twig:ButtonGroup>
            <twig:ButtonGroup class="hidden sm:flex">
                <twig:Button variant="outline" size="icon" aria-label="رجوع">
                    <twig:ux:icon name="lucide:arrow-right" />
                </twig:Button>
            </twig:ButtonGroup>
            <twig:ButtonGroup>
                <twig:Button variant="outline">أرشفة</twig:Button>
                <twig:Button variant="outline">تقرير</twig:Button>
            </twig:ButtonGroup>
            <twig:ButtonGroup>
                <twig:Button variant="outline">تأجيل</twig:Button>
                <twig:Button variant="outline" size="icon" aria-label="المزيد">
                    <twig:ux:icon name="lucide:more-horizontal" />
                </twig:Button>
            </twig:ButtonGroup>
        </twig:ButtonGroup>
    </div>
    <div dir="rtl">
        <twig:ButtonGroup>
            <twig:ButtonGroup class="hidden sm:flex">
                <twig:Button variant="outline" size="icon" aria-label="חזרה">
                    <twig:ux:icon name="lucide:arrow-right" />
                </twig:Button>
            </twig:ButtonGroup>
            <twig:ButtonGroup>
                <twig:Button variant="outline">ארכיון</twig:Button>
                <twig:Button variant="outline">דוח</twig:Button>
            </twig:ButtonGroup>
            <twig:ButtonGroup>
                <twig:Button variant="outline">דחה</twig:Button>
                <twig:Button variant="outline" size="icon" aria-label="עוד">
                    <twig:ux:icon name="lucide:more-horizontal" />
                </twig:Button>
            </twig:ButtonGroup>
        </twig:ButtonGroup>
    </div>
</div>

API Reference

Component ButtonGroup

Prop Type Description
orientation 'horizontal'|'vertical' The layout direction of the button group. Defaults to horizontal
Block Description
content The grouped buttons and/or separators

Component ButtonGroup:Separator

Prop Type Description
orientation 'horizontal'|'vertical' The separator orientation. Defaults to vertical
Block Description
content Optional custom separator content

Component ButtonGroup:Text

Prop Type Description
as 'div' The HTML tag to render. Defaults to div
Block Description
content The text content displayed in the button group