AvesraAvesrabeta

ButtonGroup

Group related buttons together with consistent styling and spacing

Import

import { AvButtonGroupImports } from '@avesra/angular';

Usage

import { Component } from '@angular/core';
import {
  AvButtonGroupImports,
  AvChipImports,
  AvDescriptionComponent,
  AvDropdownImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-button-group-basic-demo',
  imports: [
    AvButtonGroupImports,
    AvChipImports,
    AvDropdownImports,
    AvLabelComponent,
    AvDescriptionComponent,
    AppIconComponent,
  ],
  template: `<div class="flex flex-col items-start gap-6">
      <div class="flex flex-col gap-2">
        <av-dropdown>
          <av-button-group>
            <button av-button>Merge pull request</button>
            <button av-button icon-only aria-label="More options" av-dropdown-trigger>
              <span av-button-group-separator></span>
              <app-icon icon="solar:alt-arrow-down-linear" size="16" />
            </button>
          </av-button-group>
          <av-dropdown-popover class="max-w-[290px]" placement="bottom end">
            <div av-dropdown-menu>
              <div
                av-menu-item
                id="merge"
                textValue="Create a merge commit"
                class="flex flex-col items-start gap-1"
              >
                <label av-label>Create a merge commit</label>
                <p av-description>
                  All commits from this branch will be added to the base branch
                </p>
              </div>
              <div
                av-menu-item
                id="squash-and-merge"
                textValue="Squash and merge"
                class="flex flex-col items-start gap-1"
              >
                <label av-label>Squash and merge</label>
                <p av-description>
                  The 14 commits from this branch will be combined into one commit in the base
                  branch
                </p>
              </div>
              <div
                av-menu-item
                id="rebase-and-merge"
                textValue="Rebase and merge"
                class="flex flex-col items-start gap-1"
              >
                <label av-label>Rebase and merge</label>
                <p av-description>
                  The 14 commits from this branch will be rebased and added to the base branch
                </p>
              </div>
            </div>
          </av-dropdown-popover>
        </av-dropdown>
      </div>

      <div class="flex flex-col gap-2">
        <div class="flex flex-wrap gap-x-2 gap-y-4">
          <av-button-group variant="tertiary">
            <button av-button>
              <app-icon icon="solar:branching-paths-linear" size="14" />
              Fork
              <span av-chip color="accent" size="sm" variant="soft">24</span>
            </button>
            <button av-button icon-only aria-label="Fork options">
              <span av-button-group-separator></span>
              <app-icon icon="solar:alt-arrow-down-linear" size="16" />
            </button>
          </av-button-group>
          <av-button-group variant="tertiary">
            <button av-button icon-only aria-label="Scan QR code">
              <app-icon icon="solar:qr-code-linear" size="16" />
            </button>
            <button av-button>
              <span av-button-group-separator></span>
              Scan to pay
            </button>
          </av-button-group>
          <av-button-group variant="tertiary">
            <button av-button>
              <app-icon icon="solar:like-linear" size="16" />
              <span class="text-xs font-semibold">2.4K</span>
            </button>
            <button av-button icon-only aria-label="Dislike">
              <span av-button-group-separator></span>
              <app-icon icon="solar:dislike-linear" size="16" />
            </button>
          </av-button-group>
          <av-button-group variant="tertiary">
            <button av-button>
              <app-icon icon="solar:star-linear" size="14" />
              Star
            </button>
            <button av-button class="px-2">
              <span av-button-group-separator></span>
              <span av-chip color="accent" size="sm" variant="soft">104</span>
            </button>
          </av-button-group>
          <av-button-group variant="tertiary">
            <button av-button>
              <app-icon icon="solar:pin-linear" size="16" />
              Pinned
            </button>
            <button av-button icon-only aria-label="Pin options">
              <span av-button-group-separator></span>
              <app-icon icon="solar:alt-arrow-down-linear" size="16" />
            </button>
          </av-button-group>
        </div>
      </div>

      <div class="flex flex-col gap-2">
        <av-button-group variant="tertiary">
          <button av-button>
            <app-icon icon="solar:alt-arrow-left-linear" size="16" />
            Previous
          </button>
          <button av-button>
            <span av-button-group-separator></span>
            Next
            <app-icon icon="solar:alt-arrow-right-linear" size="16" />
          </button>
        </av-button-group>
      </div>

      <div class="flex flex-col gap-2">
        <av-button-group variant="tertiary">
          <button av-button>
            <app-icon icon="solar:gallery-linear" size="16" />
            Photos
          </button>
          <button av-button>
            <span av-button-group-separator></span>
            <app-icon icon="solar:videocamera-linear" size="16" />
            Videos
          </button>
          <button av-button icon-only aria-label="More options">
            <span av-button-group-separator></span>
            <app-icon icon="solar:menu-dots-linear" size="16" />
          </button>
        </av-button-group>
      </div>

      <div class="flex flex-col gap-2">
        <av-button-group variant="tertiary">
          <button av-button>Left</button>
          <button av-button>
            <span av-button-group-separator></span>
            Center
          </button>
          <button av-button>
            <span av-button-group-separator></span>
            Right
          </button>
        </av-button-group>
      </div>

      <div class="flex flex-col gap-2">
        <av-button-group variant="tertiary">
          <button av-button icon-only aria-label="Align left">
            <app-icon icon="solar:align-left-linear" size="16" />
          </button>
          <button av-button icon-only aria-label="Align top">
            <span av-button-group-separator></span>
            <app-icon icon="solar:align-top-linear" size="16" />
          </button>
          <button av-button icon-only aria-label="Align right">
            <span av-button-group-separator></span>
            <app-icon icon="solar:align-right-linear" size="16" />
          </button>
          <button av-button icon-only aria-label="Align bottom">
            <span av-button-group-separator></span>
            <app-icon icon="solar:align-bottom-linear" size="16" />
          </button>
        </av-button-group>
      </div>
    </div>`,
})
export class ButtonGroupBasicDemo {}

Anatomy

<av-button-group>
  <button av-button>First</button>
  <button av-button>
    <span av-button-group-separator></span>
    Second
  </button>
  <button av-button>
    <span av-button-group-separator></span>
    Third
  </button>
</av-button-group>

ButtonGroup wraps multiple Button components together, applying consistent styling, spacing, and automatic border radius handling. It uses context to pass size, variant, and disabled to all child buttons.

Variants

Primary

Secondary

Tertiary

Outline

Ghost

Danger

import { Component } from '@angular/core';
import { AvButtonGroupImports } from '@avesra/angular';

@Component({
  selector: 'app-button-group-variants-demo',
  imports: [AvButtonGroupImports],
  template: `<div class="flex flex-col gap-6">
  <div class="flex flex-col gap-2">
    <p class="text-sm text-muted">Primary</p>
    <av-button-group variant="primary">
      <button av-button>First</button>
      <button av-button><span av-button-group-separator></span>Second</button>
      <button av-button><span av-button-group-separator></span>Third</button>
    </av-button-group>
  </div>
  <div class="flex flex-col gap-2">
    <p class="text-sm text-muted">Secondary</p>
    <av-button-group variant="secondary">
      <button av-button>First</button>
      <button av-button><span av-button-group-separator></span>Second</button>
      <button av-button><span av-button-group-separator></span>Third</button>
    </av-button-group>
  </div>
  <div class="flex flex-col gap-2">
    <p class="text-sm text-muted">Tertiary</p>
    <av-button-group variant="tertiary">
      <button av-button>First</button>
      <button av-button><span av-button-group-separator></span>Second</button>
      <button av-button><span av-button-group-separator></span>Third</button>
    </av-button-group>
  </div>
  <div class="flex flex-col gap-2">
    <p class="text-sm text-muted">Outline</p>
    <av-button-group variant="outline">
      <button av-button>First</button>
      <button av-button><span av-button-group-separator></span>Second</button>
      <button av-button><span av-button-group-separator></span>Third</button>
    </av-button-group>
  </div>
  <div class="flex flex-col gap-2">
    <p class="text-sm text-muted">Ghost</p>
    <av-button-group variant="ghost">
      <button av-button>First</button>
      <button av-button><span av-button-group-separator></span>Second</button>
      <button av-button><span av-button-group-separator></span>Third</button>
    </av-button-group>
  </div>
  <div class="flex flex-col gap-2">
    <p class="text-sm text-muted">Danger</p>
    <av-button-group variant="danger">
      <button av-button>First</button>
      <button av-button><span av-button-group-separator></span>Second</button>
      <button av-button><span av-button-group-separator></span>Third</button>
    </av-button-group>
  </div>
</div>`,
})
export class ButtonGroupVariantsDemo {}

Sizes

Small

Medium (default)

Large

import { Component } from '@angular/core';
import { AvButtonGroupImports } from '@avesra/angular';

@Component({
  selector: 'app-button-group-sizes-demo',
  imports: [AvButtonGroupImports],
  template: `<div class="flex flex-col gap-4">
  <div class="flex flex-col items-start gap-2">
    <p class="text-sm text-muted">Small</p>
    <av-button-group size="sm" variant="secondary">
      <button av-button>First</button>
      <button av-button><span av-button-group-separator></span>Second</button>
      <button av-button><span av-button-group-separator></span>Third</button>
    </av-button-group>
  </div>
  <div class="flex flex-col items-start gap-2">
    <p class="text-sm text-muted">Medium (default)</p>
    <av-button-group size="md" variant="secondary">
      <button av-button>First</button>
      <button av-button><span av-button-group-separator></span>Second</button>
      <button av-button><span av-button-group-separator></span>Third</button>
    </av-button-group>
  </div>
  <div class="flex flex-col items-start gap-2">
    <p class="text-sm text-muted">Large</p>
    <av-button-group size="lg" variant="secondary">
      <button av-button>First</button>
      <button av-button><span av-button-group-separator></span>Second</button>
      <button av-button><span av-button-group-separator></span>Third</button>
    </av-button-group>
  </div>
</div>`,
})
export class ButtonGroupSizesDemo {}

Orientation

Use the orientation prop to arrange buttons horizontally or vertically.

Horizontal
Vertical
import { Component } from '@angular/core';
import { AvButtonGroupImports } from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-button-group-orientation-demo',
  imports: [AvButtonGroupImports, AppIconComponent],
  template: `<div class="flex items-start gap-8">
  <div class="flex flex-col gap-2">
    <span class="text-sm text-muted">Horizontal</span>
    <av-button-group orientation="horizontal" variant="tertiary">
      <button av-button icon-only aria-label="Align left">
        <app-icon icon="solar:align-left-linear" size="16" />
      </button>
      <button av-button icon-only aria-label="Align top">
        <span av-button-group-separator></span>
        <app-icon icon="solar:align-top-linear" size="16" />
      </button>
      <button av-button icon-only aria-label="Align right">
        <span av-button-group-separator></span>
        <app-icon icon="solar:align-right-linear" size="16" />
      </button>
      <button av-button icon-only aria-label="Align bottom">
        <span av-button-group-separator></span>
        <app-icon icon="solar:align-bottom-linear" size="16" />
      </button>
    </av-button-group>
  </div>
  <div class="flex flex-col gap-2">
    <span class="text-sm text-muted">Vertical</span>
    <av-button-group orientation="vertical" variant="tertiary">
      <button av-button icon-only aria-label="Align left">
        <app-icon icon="solar:align-left-linear" size="16" />
      </button>
      <button av-button icon-only aria-label="Align top">
        <span av-button-group-separator></span>
        <app-icon icon="solar:align-top-linear" size="16" />
      </button>
      <button av-button icon-only aria-label="Align right">
        <span av-button-group-separator></span>
        <app-icon icon="solar:align-right-linear" size="16" />
      </button>
      <button av-button icon-only aria-label="Align bottom">
        <span av-button-group-separator></span>
        <app-icon icon="solar:align-bottom-linear" size="16" />
      </button>
    </av-button-group>
  </div>
</div>`,
})
export class ButtonGroupOrientationDemo {}

With Icons

With icons

Icon only buttons

import { Component } from '@angular/core';
import { AvButtonGroupImports } from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-button-group-with-icons-demo',
  imports: [AvButtonGroupImports, AppIconComponent],
  template: `<div class="flex flex-col gap-6">
  <div class="flex flex-col items-start gap-2">
    <p class="text-sm text-muted">With icons</p>
    <av-button-group variant="secondary">
      <button av-button>
        <app-icon icon="solar:global-linear" size="16" />
        Search
      </button>
      <button av-button>
        <span av-button-group-separator></span>
        <app-icon icon="solar:add-square-linear" size="16" />
        Add
      </button>
      <button av-button>
        <span av-button-group-separator></span>
        <app-icon icon="solar:trash-bin-trash-linear" size="16" />
        Delete
      </button>
    </av-button-group>
  </div>
  <div class="flex flex-col items-start gap-2">
    <p class="text-sm text-muted">Icon only buttons</p>
    <av-button-group variant="tertiary">
      <button av-button icon-only aria-label="Search">
        <app-icon icon="solar:global-linear" size="16" />
      </button>
      <button av-button icon-only aria-label="Add">
        <span av-button-group-separator></span>
        <app-icon icon="solar:add-square-linear" size="16" />
      </button>
      <button av-button icon-only aria-label="Delete">
        <span av-button-group-separator></span>
        <app-icon icon="solar:trash-bin-trash-linear" size="16" />
      </button>
    </av-button-group>
  </div>
</div>`,
})
export class ButtonGroupWithIconsDemo {}

Full Width

import { Component } from '@angular/core';
import { AvButtonGroupImports } from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-button-group-full-width-demo',
  imports: [AvButtonGroupImports, AppIconComponent],
  template: `<div class="w-[400px] space-y-3">
  <av-button-group full-width>
    <button av-button>First</button>
    <button av-button><span av-button-group-separator></span>Second</button>
    <button av-button><span av-button-group-separator></span>Third</button>
  </av-button-group>
  <av-button-group full-width>
    <button av-button icon-only aria-label="Align left">
      <app-icon icon="solar:align-left-linear" size="16" />
    </button>
    <button av-button icon-only aria-label="Align top">
      <span av-button-group-separator></span>
      <app-icon icon="solar:align-top-linear" size="16" />
    </button>
    <button av-button icon-only aria-label="Align right">
      <span av-button-group-separator></span>
      <app-icon icon="solar:align-right-linear" size="16" />
    </button>
    <button av-button icon-only aria-label="Align bottom">
      <span av-button-group-separator></span>
      <app-icon icon="solar:align-bottom-linear" size="16" />
    </button>
  </av-button-group>
</div>`,
})
export class ButtonGroupFullWidthDemo {}

Disabled State

All buttons disabled

Group disabled, but one button overrides

import { Component } from '@angular/core';
import { AvButtonGroupImports } from '@avesra/angular';

@Component({
  selector: 'app-button-group-disabled-demo',
  imports: [AvButtonGroupImports],
  template: `<div class="flex flex-col gap-6">
  <div class="flex flex-col items-start gap-2">
    <p class="text-sm text-muted">All buttons disabled</p>
    <av-button-group [disabled]="true">
      <button av-button>First</button>
      <button av-button><span av-button-group-separator></span>Second</button>
      <button av-button><span av-button-group-separator></span>Third</button>
    </av-button-group>
  </div>
  <div class="flex flex-col items-start gap-2">
    <p class="text-sm text-muted">Group disabled, but one button overrides</p>
    <av-button-group [disabled]="true">
      <button av-button>First</button>
      <button av-button><span av-button-group-separator></span>Second</button>
      <button av-button [disabled]="false"><span av-button-group-separator></span>Third (enabled)</button>
    </av-button-group>
  </div>
</div>`,
})
export class ButtonGroupDisabledDemo {}

Without Separator

Simply omit the av-button-group-separator element from your buttons.

import { Component } from '@angular/core';
import { AvButtonGroupImports } from '@avesra/angular';

@Component({
  selector: 'app-button-group-without-separator-demo',
  imports: [AvButtonGroupImports],
  template: `<av-button-group>
  <button av-button>First</button>
  <button av-button>Second</button>
  <button av-button>Third</button>
</av-button-group>`,
})
export class ButtonGroupWithoutSeparatorDemo {}

Styling

Passing Tailwind CSS classes

Pass utility classes on the group and child buttons to customize color, radius, and spacing.

import { Component } from '@angular/core';
import { AvButtonGroupImports } from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-button-group-custom-styles-demo',
  imports: [
    AvButtonGroupImports,
    AppIconComponent,
  ],
  template: `<av-button-group>
      <button av-button class="rounded-none bg-[#08872B] hover:bg-[#0FBF3E]">
        Merge pull request
      </button>
      <button
        av-button
        icon-only
        aria-label="Merge options"
        class="rounded-none bg-[#08872B] hover:bg-[#0FBF3E]"
      >
        <span av-button-group-separator></span>
        <app-icon icon="solar:alt-arrow-down-linear" size="16" />
      </button>
    </av-button-group>`,
})
export class ButtonGroupCustomStylesDemo {}

Customizing the component classes

To customize the ButtonGroup classes, use the @layer components directive. Learn more.

@layer components {
  .av-button-group {
    @apply gap-2 rounded-lg;
  }

  .av-button-group__separator {
    @apply opacity-25;
  }
}

Avesra follows the BEM methodology so component variants and states stay reusable and easy to customize.

CSS Classes

The ButtonGroup component uses these CSS classes:

Base Classes

  • .av-button-group — Base button group container
  • .av-button-group--full-width — Full width modifier
  • .av-button-group--horizontal — Horizontal layout
  • .av-button-group--vertical — Vertical layout
  • .av-button-group__separator — Separator element between buttons

The ButtonGroup component automatically applies border radius to buttons:

  • First button gets rounded left/start edge
  • Last button gets rounded right/end edge
  • Middle buttons have no border radius
  • Single button gets full border radius on all edges

Add av-button-group-separator inside each Button (except the first) to show dividers between buttons.

Note

  • ButtonGroup uses context to pass size, variant, and disabled to all child Button components
  • Only direct child buttons receive the ButtonGroup props — Buttons nested inside other components (like Modal, Dropdown, etc.) will not inherit the group's props even if they are descendants of the ButtonGroup
  • Individual Button components can override the group's disabled prop by setting [disabled]="false"
  • The component automatically handles border radius between buttons
  • Add av-button-group-separator inside each Button (except the first) to show dividers between buttons
  • Buttons in a group have their active/pressed scale transform removed for a more cohesive appearance
  • When the group represents a single choice, consider Toggle Button Group instead

API Reference

Compound API across av-button-group, child button[av-button], and optional av-button-group-separator.

PropTypeDefaultDescription
variant'primary' | 'secondary' | 'tertiary' | 'outline' | 'ghost' | 'danger' | 'danger-soft'—Visual style variant applied to all buttons in the group.
size'sm' | 'md' | 'lg'—Size applied to all buttons in the group.
orientation'horizontal' | 'vertical''horizontal'The orientation of the button group.
fullWidthbooleanfalseWhether the button group should take full width of its container. Template alias: full-width.
disabledbooleanfalseWhether all buttons in the group are disabled (can be overridden on individual buttons).

Made with ❤ by SyntaxHertz. Open source and available on GitHub.