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, install the recipe and its dependencies by running:
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:
{# @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>
{# @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 }}
/>
{# @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 }}>
{# @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>
Happy coding!
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 |