Button Group

Combine multiple buttons into a unified component for toggle controls, view switchers, and option selection

Overview

Button Groups combine multiple buttons into a single unified component with connected borders. They're perfect for creating toggle controls, view switchers, text formatting toolbars, and any interface where users select from a set of related options.

Key Features

  • Single Mode - Radio button behaviour (only one active)
  • Multiple Mode - Checkbox behaviour (multiple active)
  • Horizontal & Vertical - Layout orientation options
  • Size Variants - Small, default, and large sizes
  • Programmatic Control - setValue, toggle, selectAll, deselectAll
  • Change Callbacks - React to selection changes

When to Use Button Groups

  • View Toggles - Day/Week/Month views, grid/list layouts
  • Text Formatting - Bold/Italic/Underline toolbars
  • Filter Controls - Single or multi-select filters
  • Segmented Controls - iOS-style segment pickers

Basic Usage

<!-- HTML Structure -->
<div class="btn-group" id="my-group">
    <button class="btn btn-outline active">Option 1</button>
    <button class="btn btn-outline">Option 2</button>
    <button class="btn btn-outline">Option 3</button>
</div>

<!-- JavaScript Initialisation -->
<script>
const group = Domma.elements.buttonGroup('#my-group', {
    mode: 'single',
    onChange: (value) => {
        console.log('Selected index:', value);
    }
});
</script>

Quick Start

Get started with Button Groups in seconds. Here's the simplest implementation:

CSS-Only Button Group (No JavaScript)

For visual grouping without interactivity:

CSS-Only Button Group (No JavaScript)
<div class="btn-group">
    <button type="button" class="btn btn-primary">Left</button>
    <button type="button" class="btn btn-primary">Middle</button>
    <button type="button" class="btn btn-primary">Right</button>
</div>
<div class="btn-group">
    <button class="btn btn-primary">Left</button>
    <button class="btn btn-primary">Middle</button>
    <button class="btn btn-primary">Right</button>
</div>

Interactive Toggle Group (Single Selection)

With JavaScript for radio button behaviour:

Interactive Toggle Group (Single Selection)

Selected: Day

<div class="btn-group" id="quick-start-group">
    <button type="button" class="btn btn-outline active">Day</button>
    <button type="button" class="btn btn-outline">Week</button>
    <button type="button" class="btn btn-outline">Month</button>
</div>
<p class="text-sm text-muted mt-2">Selected: <strong id="quick-start-value">Day</strong></p>
/**
 * Interactive Toggle Group (Single Selection) - from public/showcase/elements/button-group.
 */
export function mount(root) {
    const quickStartLabels = ['Day', 'Week', 'Month'];
    E.buttonGroup(root.querySelector('#quick-start-group'), {
            mode: 'single',
            onChange: (value) => {
                $(root).find('#quick-start-value').text(quickStartLabels[value]);
            }
        });
}
<div class="btn-group" id="view-toggle">
    <button class="btn btn-outline active">Day</button>
    <button class="btn btn-outline">Week</button>
    <button class="btn btn-outline">Month</button>
</div>

<script>
const viewToggle = Domma.elements.buttonGroup('#view-toggle', {
    mode: 'single',
    onChange: (value) => {
        const labels = ['Day', 'Week', 'Month'];
        console.log('Selected:', labels[value]);
    }
});
</script>

Step-by-Step Tutorial

Let's build a text formatting toolbar with bold, italic, and underline controls (multiple selection mode).

Step 1: Create the HTML Structure

Start with a container and buttons. Add the .btn-group class:

<div class="btn-group" id="format-toolbar">
    <button class="btn btn-outline">Bold</button>
    <button class="btn btn-outline">Italic</button>
    <button class="btn btn-outline">Underline</button>
</div>
<div id="format-result" class="mt-4"></div>

Step 2: Initialise with Multiple Mode

Enable checkbox behaviour by setting mode: 'multiple':

const toolbar = Domma.elements.buttonGroup('#format-toolbar', {
    mode: 'multiple'
});

Step 3: Add Change Callback

React to selection changes. The callback receives an array of active indices:

const toolbar = Domma.elements.buttonGroup('#format-toolbar', {
    mode: 'multiple',
    onChange: (values) => {
        const labels = ['Bold', 'Italic', 'Underline'];
        const active = values.map(i => labels[i]).join(', ');
        $('#format-result').text(
            active || 'No formatting selected';
    }
});

Step 4: Add Control Buttons

Provide programmatic control with select all and clear all:

<button id="select-all" class="btn btn-sm btn-secondary">Select All</button>
<button id="clear-all" class="btn btn-sm btn-secondary">Clear All</button>

<script>
$('#select-all').on('click', () => {
    toolbar.selectAll();
});

$('#clear-all').on('click', () => {
    toolbar.deselectAll();
});
</script>

Step 5: Add Visual Feedback

Apply styles based on the selected formatting:

const toolbar = Domma.elements.buttonGroup('#format-toolbar', {
    mode: 'multiple',
    onChange: (values) => {
        const result = $('#format-result');
        const labels = ['Bold', 'Italic', 'Underline'];

        // Update text
        const active = values.map(i => labels[i]).join(', ');
        result.textContent = active || 'No formatting selected';

        // Apply CSS styles
        result.style.fontWeight = values.includes(0) ? 'bold' : 'normal';
        result.style.fontStyle = values.includes(1) ? 'italic' : 'normal';
        result.style.textDecoration = values.includes(2) ? 'underline' : 'none';
    }
});

Step 6: Complete Working Demo

Try the complete formatting toolbar:

Step 6: Complete Working Demo
Select formatting options above
<div class="btn-group" id="tutorial-toolbar">
    <button type="button" class="btn btn-outline">Bold</button>
    <button type="button" class="btn btn-outline">Italic</button>
    <button type="button" class="btn btn-outline">Underline</button>
</div>
<div class="flex gap-2 mt-3">
    <button type="button" id="tutorial-select-all" class="btn btn-sm btn-secondary">Select All</button>
    <button type="button" id="tutorial-clear-all" class="btn btn-sm btn-secondary">Clear All</button>
</div>
<div id="tutorial-result" class="mt-4 p-4 bg-surface rounded border button-group-min-h-40">
    Select formatting options above
</div>
/**
 * Step 6: Complete Working Demo - from public/showcase/elements/button-group.
 */
export function mount(root) {
        // Tutorial Demo
    const tutorialLabels = ['Bold', 'Italic', 'Underline'];
    const tutorialToolbar = E.buttonGroup(root.querySelector('#tutorial-toolbar'), {
            mode: 'multiple',
            onChange: (values) => {
              const result = $(root).find('#tutorial-result');
                const active = values.map(i => tutorialLabels[i]).join(', ');
              result.text(active || 'No formatting selected');

                // Apply visual styles
              result.css({
                'font-weight': values.includes(0) ? 'bold' : 'normal',
                'font-style': values.includes(1) ? 'italic' : 'normal',
                'text-decoration': values.includes(2) ? 'underline' : 'none'
              });
            }
        });
    $(root).find('#tutorial-select-all').on('click', () => {
            tutorialToolbar.selectAll();
        });
    $(root).find('#tutorial-clear-all').on('click', () => {
            tutorialToolbar.deselectAll();
        });
}

Examples & Variations

View Toggle (Single Selection)

Radio button behaviour - only one option active at a time:

View Toggle (Single Selection)

Selected: Day

<div class="btn-group" id="view-toggle">
    <button type="button" class="btn btn-outline active">Day</button>
    <button type="button" class="btn btn-outline">Week</button>
    <button type="button" class="btn btn-outline">Month</button>
</div>
<p class="text-sm text-muted mt-2">Selected: <strong id="view-value">Day</strong></p>
/**
 * View Toggle (Single Selection) - from public/showcase/elements/button-group.
 */
export function mount(root) {
        // Examples: View Toggle
    const viewLabels = ['Day', 'Week', 'Month'];
    E.buttonGroup(root.querySelector('#view-toggle'), {
            mode: 'single',
            onChange: (value) => {
                $(root).find('#view-value').text(viewLabels[value]);
            }
        });
}
const viewToggle = Domma.elements.buttonGroup('#view-toggle', {
    mode: 'single',
    onChange: (value) => {
        const labels = ['Day', 'Week', 'Month'];
        $('#view-value').text(labels[value]);
    }
});

Format Toolbar (Multiple Selection)

Checkbox behaviour - multiple options can be active:

Format Toolbar (Multiple Selection)

Active: None

<div class="btn-group" id="format-toggle">
    <button type="button" class="btn btn-outline">Bold</button>
    <button type="button" class="btn btn-outline">Italic</button>
    <button type="button" class="btn btn-outline">Underline</button>
</div>
<p class="text-sm text-muted mt-2">Active: <strong id="format-value">None</strong></p>
/**
 * Format Toolbar (Multiple Selection) - from public/showcase/elements/button-group.
 */
export function mount(root) {
        // Examples: Format Toolbar
    const formatLabels = ['Bold', 'Italic', 'Underline'];
    E.buttonGroup(root.querySelector('#format-toggle'), {
            mode: 'multiple',
            onChange: (values) => {
                const active = values.map(i => formatLabels[i]).join(', ');
                $(root).find('#format-value').text(active || 'None');
            }
        });
}
const formatToggle = Domma.elements.buttonGroup('#format-toggle', {
    mode: 'multiple',
    onChange: (values) => {
        const labels = ['Bold', 'Italic', 'Underline'];
        const active = values.map(i => labels[i]).join(', ');
        $('#format-value').text(active || 'None');
    }
});

Vertical Button Group

Stack buttons vertically with .btn-group-vertical:

Vertical Button Group
<div class="btn-group-vertical" id="vertical-group">
    <button type="button" class="btn btn-secondary active">Top</button>
    <button type="button" class="btn btn-secondary">Middle</button>
    <button type="button" class="btn btn-secondary">Bottom</button>
</div>
/**
 * Vertical Button Group - from public/showcase/elements/button-group.
 */
export function mount(root) {
        // Examples: Vertical Group
    E.buttonGroup(root.querySelector('#vertical-group'), {
            mode: 'single'
        });
}
<div class="btn-group-vertical" id="vertical-group">
    <button class="btn btn-secondary active">Top</button>
    <button class="btn btn-secondary">Middle</button>
    <button class="btn btn-secondary">Bottom</button>
</div>

<script>
Domma.elements.buttonGroup('#vertical-group', {
    mode: 'single'
});
</script>

Size Variants

Small, default, and large button groups:

Size Variants
<div class="flex flex-col gap-4">
    <div class="btn-group">
        <button type="button" class="btn btn-primary btn-sm active">Small</button>
        <button type="button" class="btn btn-primary btn-sm">Group</button>
    </div>
    <div class="btn-group">
        <button type="button" class="btn btn-primary active">Default</button>
        <button type="button" class="btn btn-primary">Group</button>
    </div>
    <div class="btn-group">
        <button type="button" class="btn btn-primary btn-lg active">Large</button>
        <button type="button" class="btn btn-primary btn-lg">Group</button>
    </div>
</div>
<!-- Small -->
<div class="btn-group">
    <button class="btn btn-primary btn-sm">Small</button>
    <button class="btn btn-primary btn-sm">Group</button>
</div>

<!-- Default -->
<div class="btn-group">
    <button class="btn btn-primary">Default</button>
    <button class="btn btn-primary">Group</button>
</div>

<!-- Large -->
<div class="btn-group">
    <button class="btn btn-primary btn-lg">Large</button>
    <button class="btn btn-primary btn-lg">Group</button>
</div>

Allow Empty Selection

In single mode, allow deselecting all buttons:

Allow Empty Selection

Selected: None

<div class="btn-group" id="optional-group">
    <button type="button" class="btn btn-outline">Option A</button>
    <button type="button" class="btn btn-outline">Option B</button>
    <button type="button" class="btn btn-outline">Option C</button>
</div>
<p class="text-sm text-muted mt-2">Selected: <strong id="optional-value">None</strong></p>
/**
 * Allow Empty Selection - from public/showcase/elements/button-group.
 */
export function mount(root) {
        // Examples: Allow Empty
    const optionalLabels = ['Option A', 'Option B', 'Option C'];
    E.buttonGroup(root.querySelector('#optional-group'), {
            mode: 'single',
            allowEmpty: true,
            onChange: (value) => {
                $(root).find('#optional-value').text(value !== null ? optionalLabels[value] : 'None');
            }
        });
}
const optionalGroup = Domma.elements.buttonGroup('#optional-group', {
    mode: 'single',
    allowEmpty: true,  // Allow no selection
    onChange: (value) => {
        const labels = ['Option A', 'Option B', 'Option C'];
        $('#optional-value').text(
            value !== null ? labels[value] : 'None';
    }
});

Programmatic Control

Control selections via JavaScript methods:

Programmatic Control

Current value: []

<div class="btn-group" id="programmatic-group">
    <button type="button" class="btn btn-outline">Feature A</button>
    <button type="button" class="btn btn-outline">Feature B</button>
    <button type="button" class="btn btn-outline">Feature C</button>
</div>
<div class="flex gap-2 mt-3">
    <button type="button" id="prog-set-0" class="btn btn-sm btn-secondary">Select First</button>
    <button type="button" id="prog-set-1" class="btn btn-sm btn-secondary">Select Second</button>
    <button type="button" id="prog-toggle-2" class="btn btn-sm btn-secondary">Toggle Third</button>
    <button type="button" id="prog-clear" class="btn btn-sm btn-secondary">Clear</button>
</div>
<p class="text-sm text-muted mt-2">Current value: <strong id="prog-value">[]</strong></p>
/**
 * Programmatic Control - from public/showcase/elements/button-group.
 */
export function mount(root) {
        // Examples: Programmatic Control
    const progGroup = E.buttonGroup(root.querySelector('#programmatic-group'), {
            mode: 'multiple',
            onChange: (values) => {
                $(root).find('#prog-value').text(JSON.stringify(values));
            }
        });
    $(root).find('#prog-set-0').on('click', () => progGroup.setValue([0]));
    $(root).find('#prog-set-1').on('click', () => progGroup.setValue([1]));
    $(root).find('#prog-toggle-2').on('click', () => progGroup.toggle(2));
    $(root).find('#prog-clear').on('click', () => progGroup.deselectAll());
}
const group = Domma.elements.buttonGroup('#programmatic-group', {
    mode: 'multiple',
    onChange: (values) => {
        $('#prog-value').text(JSON.stringify(values));
    }
});

// Programmatic methods
$('#prog-set-0').on('click', () => {
    group.setValue([0]);  // Set active buttons by index
});

$('#prog-set-1').on('click', () => {
    group.setValue([1]);
});

$('#prog-toggle-2').on('click', () => {
    group.toggle(2);  // Toggle specific button
});

$('#prog-clear').on('click', () => {
    group.deselectAll();  // Clear all selections
});

Icon Button Group

Use icons for compact visual controls:

Icon Button Group
<div class="btn-group" id="icon-group">
    <button type="button" class="btn btn-outline active" title="Left Align">
        <svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
            <path d="M2 12.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"></path>
        </svg>
    </button>
    <button type="button" class="btn btn-outline" title="Centre Align">
        <svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
            <path d="M4 12.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"></path>
        </svg>
    </button>
    <button type="button" class="btn btn-outline" title="Right Align">
        <svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
            <path d="M6 12.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-4-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm4-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-4-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"></path>
        </svg>
    </button>
</div>
/**
 * Icon Button Group - from public/showcase/elements/button-group.
 */
export function mount(root) {
        // Examples: Icon Group
    E.buttonGroup(root.querySelector('#icon-group'), {
            mode: 'single'
        });
}
<div class="btn-group" id="icon-group">
    <button class="btn btn-outline active" title="Left Align">
        <svg width="16" height="16">...</svg>
    </button>
    <button class="btn btn-outline" title="Centre Align">
        <svg width="16" height="16">...</svg>
    </button>
    <button class="btn btn-outline" title="Right Align">
        <svg width="16" height="16">...</svg>
    </button>
</div>

<script>
Domma.elements.buttonGroup('#icon-group', {
    mode: 'single'
});
</script>

Real-World Example: Filter Panel

Build a product filter with category and price range controls:

Real-World Example: Filter Panel
Active Filters:
None selected
<div class="mb-4">
    <label class="text-sm font-semibold mb-2 block-button-group">Category:</label>
    <div class="btn-group" id="category-filter">
        <button type="button" class="btn btn-outline btn-sm">All</button>
        <button type="button" class="btn btn-outline btn-sm">Electronics</button>
        <button type="button" class="btn btn-outline btn-sm">Clothing</button>
        <button type="button" class="btn btn-outline btn-sm">Books</button>
    </div>
</div>
<div class="mb-4">
    <label class="text-sm font-semibold mb-2 block-button-group">Price Range:</label>
    <div class="btn-group" id="price-filter">
        <button type="button" class="btn btn-outline btn-sm">Under £20</button>
        <button type="button" class="btn btn-outline btn-sm">£20-£50</button>
        <button type="button" class="btn btn-outline btn-sm">£50-£100</button>
        <button type="button" class="btn btn-outline btn-sm">Over £100</button>
    </div>
</div>
<div class="p-3 bg-surface rounded border">
    <strong>Active Filters:</strong>
    <div id="filter-summary" class="text-sm text-muted mt-1">None selected</div>
</div>
/**
 * Real-World Example: Filter Panel - from public/showcase/elements/button-group.
 */
export function mount(root) {
        // Examples: Filter Panel
    const categoryLabels = ['All', 'Electronics', 'Clothing', 'Books'];
    const priceLabels = ['Under £20', '£20-£50', '£50-£100', 'Over £100'];
    const categoryFilter = E.buttonGroup(root.querySelector('#category-filter'), {
            mode: 'single',
            allowEmpty: true,
            onChange: updateFilters
        });
    const priceFilter = E.buttonGroup(root.querySelector('#price-filter'), {
            mode: 'single',
            allowEmpty: true,
            onChange: updateFilters
        });
    function updateFilters() {
            const category = categoryFilter.getValue();
            const price = priceFilter.getValue();

            const filters = [];
            if (category !== null) filters.push(categoryLabels[category]);
            if (price !== null) filters.push(priceLabels[price]);

            $(root).find('#filter-summary').text(filters.length ? filters.join(', ') : 'None selected');
        }
}
// Category filter (single selection, allow empty)
const categoryFilter = Domma.elements.buttonGroup('#category-filter', {
    mode: 'single',
    allowEmpty: true,
    onChange: updateFilters
});

// Price filter (single selection, allow empty)
const priceFilter = Domma.elements.buttonGroup('#price-filter', {
    mode: 'single',
    allowEmpty: true,
    onChange: updateFilters
});

function updateFilters() {
    const categories = ['All', 'Electronics', 'Clothing', 'Books'];
    const prices = ['Under £20', '£20-£50', '£50-£100', 'Over £100'];

    const category = categoryFilter.getValue();
    const price = priceFilter.getValue();

    const filters = [];
    if (category !== null) filters.push(categories[category]);
    if (price !== null) filters.push(prices[price]);

    $('#filter-summary').text(
        filters.length ? filters.join(', ') : 'None selected';
}

Best Practices

Accessibility

  • Use Semantic Buttons - Always use elements, not or ``tags

Performance

  • Single Initialisation - Initialise once, use programmatic methods for changes
  • Event Delegation - Component uses event delegation internally - no need to rebind after updates
  • Destroy When Removed - Call .destroy() when removing from DOM to clean up listeners
  • Throttle onChange - If onChange triggers expensive operations, use _.throttle() or _.debounce()
  • Cache DOM Queries - Store component instance instead of re-selecting: const group = Domma.elements.buttonGroup(...)
  • Get Button Elements - Use getActive() to access actual button elements, not just indices

Common Gotchas

  • Issue: onChange callback not firing
    Solution: Ensure buttons have class .btn - component only tracks elements with this class
    Example: Click
  • Issue: getValue() returns null in single mode
    Solution: No button is active. Either set initial active button in HTML or use allowEmpty: false

Example: Default

  • Issue: selectAll() doesn't work
    Solution: selectAll() only works in mode: 'multiple', not single mode
    Example: { mode: 'multiple' }
  • Issue: Buttons visually separate instead of connected
    Solution: Missing .btn-group class on container
    Example: ````
  • Issue: Active class not changing
    Solution: Using custom activeClass? Ensure CSS styles it correctly
    Example: { activeClass: 'selected' } requires .selected { ... } CSS

Tips & Tricks

  • Default Selection - Add .active class in HTML to pre-select buttons
  • Mixed Sizes - All buttons in a group should use the same size class for visual consistency
  • Outline Style - Use .btn-outline for toggle groups, .btn-primary for static groups
  • Vertical on Mobile - Switch to .btn-group-vertical on small screens for better touch targets
  • Store Instance - Save returned instance to call methods later: const group = Domma.elements.buttonGroup(...)
  • Get Button Elements - Use getActive() to access actual button elements, not just indices
<!-- Accessible Button Group -->
<div class="btn-group" role="group" aria-label="Text alignment">
    <button class="btn btn-outline" aria-pressed="false" aria-label="Align left">
        <svg>...</svg>
    </button>
    <button class="btn btn-outline active" aria-pressed="true" aria-label="Align centre">
        <svg>...</svg>
    </button>
    <button class="btn btn-outline" aria-pressed="false" aria-label="Align right">
        <svg>...</svg>
    </button>
</div>

<script>
const group = Domma.elements.buttonGroup('.btn-group', {
    mode: 'single',
    onChange: (value, index, button) => {
        // Update ARIA states
        group.buttons.forEach((btn, i) => {
            btn.setAttribute('aria-pressed', i === value);
        });
    }
});
</script>

API Reference

Constructor Options

Option Type Default Description
mode String 'single' Selection mode. 'single' = radio (one active), 'multiple' = checkbox (many active)
activeClass String 'active' CSS class applied to active/selected buttons
allowEmpty Boolean false In single mode, allow deselecting all buttons (clicking active button deselects it)
onChange Function null Callback fired when selection changes. Receives (value, index, button). Value is index (single) or array (multiple)

Methods

Method Parameters Returns Description
getValue() - Number | Number[] | null Get active button index (single mode) or array of indices (multiple mode). Returns null if none active
getActive() - HTMLElement | HTMLElement[] | null Get active button element (single) or array of elements (multiple). Returns null if none active
setValue(value) Number | Number[] this Set active button(s) by index. Pass single index (single mode) or array of indices (multiple). Chainable
toggle(index) Number this Toggle a specific button by index. In single mode, selects it. In multiple mode, toggles it. Chainable
selectAll() - this Select all buttons. Only works in mode: 'multiple'. Chainable
deselectAll() - this Deselect all buttons. Works in both single and multiple modes. Chainable
destroy() - void Remove event listeners and clean up. Call before removing from DOM

Events / Callbacks

Event Parameters Description
onChange (value, index, button) Fired when selection changes (button clicked or programmatic change).
value: Active index (single) or array of indices (multiple)
index: Index of button that triggered change (-1 for selectAll/deselectAll)
button: Button element that triggered change (null for selectAll/deselectAll)

CSS Classes

Class Description
.btn-group Container class for horizontal button groups. Connects borders and removes gaps between buttons
.btn-group-vertical Container class for vertical button groups. Stacks buttons vertically with connected borders
.btn Required base class for all buttons within the group. Component only tracks elements with this class
.active Default class applied to selected/active buttons. Can be customised via activeClass option
.btn-primary Primary button style (filled blue). Good for static groups
.btn-secondary Secondary button style (filled grey)
.btn-outline Outline button style (transparent with border). Recommended for toggle groups
.btn-sm Small button size
.btn-lg Large button size
// Complete API Example
const group = Domma.elements.buttonGroup('#my-group', {
    mode: 'multiple',
    activeClass: 'selected',
    allowEmpty: true,
    onChange: (value, index, button) => {
        console.log('Selection changed:', value);
        console.log('Triggered by button at index:', index);
    }
});

// Methods
group.setValue([0, 2]);           // Select first and third buttons
const active = group.getValue();  // Get active indices: [0, 2]
const buttons = group.getActive(); // Get button elements
group.toggle(1);                  // Toggle second button
group.selectAll();                // Select all (multiple mode only)
group.deselectAll();              // Clear all selections
group.destroy();                  // Clean up when done

Config Engine Integration

Use Domma's declarative $.setup() system to configure Button Groups without manual initialisation.

Basic Setup

$.setup({
    '#view-toggle': {
        component: 'buttonGroup',
        options: {
            mode: 'single',
            onChange: (value) => {
                console.log('Selected:', value);
            }
        }
    }
});

Multiple Button Groups

$.setup({
    // View selector (single mode)
    '#view-selector': {
        component: 'buttonGroup',
        options: {
            mode: 'single',
            allowEmpty: false,
            onChange: (value) => {
                updateView(['day', 'week', 'month'][value]);
            }
        }
    },

    // Format toolbar (multiple mode)
    '#format-toolbar': {
        component: 'buttonGroup',
        options: {
            mode: 'multiple',
            onChange: (values) => {
                applyFormatting(values);
            }
        }
    },

    // Category filter
    '#category-filter': {
        component: 'buttonGroup',
        options: {
            mode: 'single',
            allowEmpty: true,
            activeClass: 'selected'
        }
    }
});

Live Demo

Button Group configured via Config Engine:

Live Demo

Selected: Option 1

<div class="btn-group" id="config-demo-group">
    <button type="button" class="btn btn-outline active">Option 1</button>
    <button type="button" class="btn btn-outline">Option 2</button>
    <button type="button" class="btn btn-outline">Option 3</button>
</div>
<p class="text-sm text-muted mt-2">Selected: <strong id="config-demo-value">Option 1</strong></p>
/**
 * Live Demo - from public/showcase/elements/button-group.
 */
export function mount(root) {
        // Config Engine Demo
    $.setup({
            '#config-demo-group': {
                component: 'buttonGroup',
                options: {
                    mode: 'single',
                    onChange: (value) => {
                        const labels = ['Option 1', 'Option 2', 'Option 3'];
                        $(root).find('#config-demo-value').text(labels[value]);
                    }
                }
            }
        });
}
// Configuration
$.setup({
    '#config-demo-group': {
        component: 'buttonGroup',
        options: {
            mode: 'single',
            onChange: (value) => {
                const labels = ['Option 1', 'Option 2', 'Option 3'];
                $('#config-demo-value').text(labels[value]);
            }
        }
    }
});

// Access instance later
const group = Domma.elements.get('#config-demo-group');
group.setValue(1);  // Programmatic control still works

CSS Customisation

Override these CSS variables to customise Button Group appearance and match your design system.

Variable Default Controls
--dm-primary var(--dm-blue-600) Active button colour
--dm-primary-hover var(--dm-blue-700) Active button hover
--dm-border var(--dm-slate-300) Button border colour
--dm-radius-md 0.375rem Button corner radius

Example Override

:root {
    /* Flat button group */
    --dm-border: transparent;
}

Full CSS Customisation Cheat-Sheet →

Related Elements

Related Elements
<div class="grid grid-cols-1 gap-4 grid-auto-fit-250"><a href="/showcase/elements/tabs" class="card card-hover">
        <div class="card-body">
            <h3 class="card-title">Tabs</h3>
            <p class="card-text">Organise content into tabbed panels. Button Groups work great as custom tab
                controls with manual tab switching.</p>
        </div>
    </a>

    <a href="/showcase/elements/forms" class="card card-hover">
        <div class="card-body">
            <h3 class="card-title">Forms</h3>
            <p class="card-text">Form components including checkboxes, radios, and input groups. Button Groups
                provide a more visual alternative to radio/checkbox inputs.</p>
        </div>
    </a>

    <a href="/showcase/elements/dropdown" class="card card-hover">
        <div class="card-body">
            <h3 class="card-title">Dropdown</h3>
            <p class="card-text">Toggleable dropdown menus. Combine with Button Groups to create split button
                dropdowns or segmented controls.</p>
        </div>
    </a>

    <a href="/showcase/elements/navbar" class="card card-hover">
        <div class="card-body">
            <h3 class="card-title">Navbar</h3>
            <p class="card-text">Responsive navigation bars. Button Groups can be embedded in navbars for view
                toggles and action groups.</p>
        </div>
    </a>

    <a href="/showcase/elements/list-group" class="card card-hover">
        <div class="card-body">
            <h3 class="card-title">List Group</h3>
            <p class="card-text">Selectable list items for single or multi-select scenarios where a list layout suits better than a button row.</p>
        </div>
    </a>
</div>