View as Markdown

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

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

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 app:

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

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

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.
Loading...
<twig:ButtonGroup aria-label="Button group">
    <twig:Button>Button 1</twig:Button>
    <twig:Button>Button 2</twig:Button>
</twig:ButtonGroup>

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

<twig:ButtonGroup>

Prop Type Default
orientation 
'horizontal'|'vertical' 'horizontal'
Block Description
content The grouped buttons and/or separators.

<twig:ButtonGroup:Separator>

Prop Type Default
orientation 
'horizontal'|'vertical' 'vertical'

<twig:ButtonGroup:Text>

Prop Type Default
as 
'div' 'div'
Block Description
content The text content displayed in the button group.