Button Group

A layout helper for grouping buttons and related controls with shared borders and separators.

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" class="size-4" />
        </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" class="size-4" />
            Snooze
        </twig:Button>
        <twig:Button variant="outline" size="icon" aria-label="More options">
            <twig:ux:icon name="lucide:more-horizontal" class="size-4" />
        </twig:Button>
    </twig:ButtonGroup>
</twig:ButtonGroup>

Installation

Ensure the Symfony UX Toolkit is installed in your Symfony app:

$ composer require --dev symfony/ux-toolkit

Then, run the following command to install the component and its dependencies:

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

The UX Toolkit is not mandatory to install a component. You can install it manually by following the next steps:

  1. Copy the following file(s) into your Symfony app:
    templates/components/ButtonGroup.html.twig
    {# @prop orientation 'horizontal'|'vertical' The orientation, default to `horizontal` #}
    {# @block content The default block #}
    {%- props orientation = 'horizontal' -%}
    {%- set style = html_cva(
        base: 'flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*=\'w-\'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2',
        variants: {
            orientation: {
                horizontal: '[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none',
                vertical: 'flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none',
            },
        },
        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 orientation of the separator, default to `vertical` #}
    {# @block content The default block #}
    {%- props orientation = 'vertical' -%}
    <twig:Separator
        orientation="{{ orientation }}"
        data-slot="button-group-separator"
        class="bg-input relative m-0! self-stretch {{ orientation == 'vertical' ? 'h-auto' }} {{ attributes.render('class')|tailwind_merge }}"
        {{ ...attributes }}
    />
    
    templates/components/ButtonGroup/Text.html.twig
    {# @prop as 'div' The HTML tag to use, default to `div` #}
    {# @block content The default block #}
    {%- props as = 'div' -%}
    <{{ as }}
        data-slot="button-group-text"
        class="{{ 'bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_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 orientation of the separator, default to `horizontal` #}
    {# @prop decorative boolean Whether the separator is decorative or not, default to `true` #}
    {%- props orientation = 'horizontal', decorative = true -%}
    {%- set style = html_cva(
        base: 'shrink-0 bg-border',
        variants: {
            orientation: {
                horizontal: 'h-[1px] w-full',
                vertical: 'h-full w-[1px]',
            },
        },
    ) -%}
    <div
        class="{{ style.apply({orientation: orientation, decorative: decorative}, attributes.render('class'))|tailwind_merge }}"
        {{ attributes.defaults({
            role: decorative ? 'none' : 'separator',
            'aria-orientation': decorative ? false : orientation,
        }) }}
    ></div>
    
  2. If necessary, 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
  3. And the most important, enjoy!

Usage

<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" class="size-4" />
        </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" class="size-4" />
            Snooze
        </twig:Button>
        <twig:Button variant="outline" size="icon" aria-label="More options">
            <twig:ux:icon name="lucide:more-horizontal" class="size-4" />
        </twig:Button>
    </twig:ButtonGroup>
</twig:ButtonGroup>

Examples

Default

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" class="size-4" />
        </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" class="size-4" />
            Snooze
        </twig:Button>
        <twig:Button variant="outline" size="icon" aria-label="More options">
            <twig:ux:icon name="lucide:more-horizontal" class="size-4" />
        </twig:Button>
    </twig:ButtonGroup>
</twig:ButtonGroup>

Orientation

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

Size

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" class="size-4" />
        </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" class="size-4" />
        </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" class="size-5" />
        </twig:Button>
    </twig:ButtonGroup>
</div>

Input

Loading...
<twig:ButtonGroup class="max-w-md">
    <twig:Input placeholder="Search..." />
    <twig:Button size="icon-lg" variant="outline" aria-label="Search">
        <twig:ux:icon name="lucide:search" class="size-4" />
    </twig:Button>
</twig:ButtonGroup>

Nested

Loading...
<twig:ButtonGroup>
    <twig:ButtonGroup>
        <twig:Button variant="outline" size="sm">1</twig:Button>
        <twig:Button variant="outline" size="sm">2</twig:Button>
        <twig:Button variant="outline" size="sm">3</twig:Button>
        <twig:Button variant="outline" size="sm">4</twig:Button>
        <twig:Button variant="outline" size="sm">5</twig:Button>
    </twig:ButtonGroup>
    <twig:ButtonGroup>
        <twig:Button variant="outline" size="icon-sm" aria-label="Previous">
            <twig:ux:icon name="lucide:arrow-left" class="size-4" />
        </twig:Button>
        <twig:Button variant="outline" size="icon-sm" aria-label="Next">
            <twig:ux:icon name="lucide:arrow-right" class="size-4" />
        </twig:Button>
    </twig:ButtonGroup>
</twig:ButtonGroup>

Separator

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

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" class="size-4" />
    </twig:Button>
</twig:ButtonGroup>

API Reference

ButtonGroup

Prop Type Description
orientation 'horizontal'|'vertical' The orientation, default to horizontal
Block Description
content The default block

ButtonGroup:Separator

Prop Type Description
orientation 'horizontal'|'vertical' The orientation of the separator, default to vertical
Block Description
content The default block

ButtonGroup:Text

Prop Type Description
as 'div' The HTML tag to use, default to div
Block Description
content The default block