Button Group
Button groups are powerful UI components that combine multiple related buttons into a single, cohesive unit. They're perfect for creating toolbars, navigation sets, or any interface where multiple actions need to be visually connected. Button groups improve user experience by organizing related actions, reducing visual clutter, and providing clear interaction patterns for common tasks.
Basic Examples
Create a basic button group using the .btn-group
class to wrap a series of buttons.
Each button maintains its individual styling while appearing as part of a unified set. Add the
role="group"
attribute and an appropriate aria-label
to enhance
accessibility. Remixicon icons enhance visual appeal and improve button recognition.
Use the .active
class to highlight the currently selected button, and enhance
accessibility with aria-current="page"
. This visual feedback helps users understand
their current context within the interface and indicates which option is currently selected or
active.
Mixed Styles
In a button group, buttons can be styled with different contextual color classes to represent distinct actions or statuses. This approach helps users quickly identify the purpose and importance of each button based on color semantics. For example, red typically indicates destructive actions, yellow for caution, and green for confirmation.
Outlined Styles
Button groups with outlined buttons use the .btn-outline-*
classes to create a more
subtle appearance with transparent backgrounds and colored borders. Outlined button groups are
ideal for secondary actions, less prominent toolbars, or interfaces where you want to reduce
visual weight while maintaining functionality.
Checkbox and Radio Buttons
Transform checkboxes and radio inputs into toggle buttons within a button group to create
intuitive selection interfaces. Use the .btn-check
class to hide the default input
appearance while maintaining functionality. Checkbox button groups allow multiple selections
(like formatting options), while radio button groups enforce single selection (like alignment
options).
Button Toolbar
Combine multiple button groups into a comprehensive toolbar using the
.btn-toolbar
class. Button toolbars organize related actions into logical groups,
similar to application toolbars in desktop software. Add role="toolbar"
and
aria-label
attributes to improve accessibility, and use margin utilities like
.me-2
to create proper spacing between button groups.
Button toolbars can be enhanced with input groups to create complex control interfaces. Use
.justify-content-between
to create space between groups, or combine with other flex
utilities to achieve custom layouts. These examples demonstrate browser-like navigation controls
and media player interfaces.
Size Variations
Adjust button group sizes using the .btn-group-xl
, .btn-group-lg
,
.btn-group-sm
, and .btn-group-xs
classes. Size variations help create
visual hierarchy and adapt button groups to different interface contexts. Larger button groups
work well for primary actions, while smaller sizes are suitable for compact toolbars or dense
interfaces.
Nested Dropdowns
Create more complex button groups by nesting dropdown menus within them. This pattern is useful
for interfaces with primary actions displayed as buttons and secondary actions hidden in a
dropdown menu. The dropdown toggle button should be wrapped in its own
.btn-group
to maintain proper styling and behavior.
Vertical Variations
Create vertical button stacks using the .btn-group-vertical
class instead of
.btn-group
. Vertical button groups are ideal for side navigation, action panels, or
interfaces where vertical space is more available than horizontal space. They maintain all the
functionality of standard button groups but with a vertical orientation.
Vertical button groups can also include dropdowns for more complex navigation structures. Note
that for dropdowns at the bottom of a vertical group, use the .dropup
class to
ensure the menu opens in the correct direction.
Radio buttons can also be arranged vertically, creating intuitive option selectors for settings or preferences that are better displayed in a vertical layout.
Interactive Button Groups
Create dynamic button groups that respond to user interactions with state management, real-time updates, and advanced functionality. These examples demonstrate button groups with JavaScript integration for complex user interfaces and interactive applications.
Specialized Use Cases
Industry-specific button group implementations for specialized applications like content editors, media players, design tools, and data management interfaces. These examples show how button groups can be tailored for specific workflows and professional applications.
Advanced Patterns & Layouts
Complex button group patterns that combine multiple concepts, including responsive layouts, conditional visibility, state synchronization, and integration with other UI components. These patterns are useful for sophisticated applications with complex user interaction requirements.
Developer Tips & Best Practices
Pro Tips for Button Groups
- State Management: Use data attributes or JavaScript to maintain button states across interactions
- Performance: Implement event delegation for large button groups to reduce memory usage
- Responsive Design: Consider stacking button groups vertically on mobile devices
- Loading States: Show loading indicators in buttons during async operations
- Keyboard Navigation: Implement arrow key navigation for better accessibility
- Icon Consistency: Maintain consistent icon styles and sizes across all buttons
Common Pitfalls to Avoid
- Don't use too many buttons in a single group - it becomes overwhelming for users
- Avoid mixing different button styles within the same group unless there's a clear purpose
- Don't forget to handle keyboard navigation and focus management properly
- Never use button groups for navigation that should be handled by proper link elements
- Don't make button groups too small on touch devices - ensure adequate touch targets
- Avoid using button groups for single actions - they're meant for related actions
JavaScript Integration Example
Here's a comprehensive button group manager for dynamic applications:
class ButtonGroupManager {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.activeButtons = new Set();
this.callbacks = new Map();
this.init();
}
init() {
this.container.addEventListener('click', this.handleClick.bind(this));
this.container.addEventListener('keydown', this.handleKeydown.bind(this));
}
// Handle button clicks with state management
handleClick(event) {
const button = event.target.closest('button');
if (!button) return;
const groupElement = button.closest('.btn-group, .btn-group-vertical');
const groupType = groupElement.dataset.groupType || 'single';
const buttonId = button.dataset.buttonId;
this.updateButtonState(button, groupElement, groupType);
this.executeCallback(buttonId, button);
}
// Update button states based on group type
updateButtonState(button, group, type) {
const buttons = group.querySelectorAll('button');
switch (type) {
case 'single':
// Single selection - deactivate others
buttons.forEach(btn => {
btn.classList.remove('active');
btn.setAttribute('aria-pressed', 'false');
});
button.classList.add('active');
button.setAttribute('aria-pressed', 'true');
break;
case 'multiple':
// Multiple selection - toggle current
const isActive = button.classList.contains('active');
button.classList.toggle('active');
button.setAttribute('aria-pressed', (!isActive).toString());
break;
case 'toggle':
// Toggle group - all on or all off
const allActive = Array.from(buttons).every(btn =>
btn.classList.contains('active')
);
buttons.forEach(btn => {
btn.classList.toggle('active', !allActive);
btn.setAttribute('aria-pressed', (!allActive).toString());
});
break;
}
}
// Keyboard navigation support
handleKeydown(event) {
const button = event.target;
if (!button.matches('button')) return;
const group = button.closest('.btn-group, .btn-group-vertical');
const buttons = Array.from(group.querySelectorAll('button'));
const currentIndex = buttons.indexOf(button);
const isVertical = group.classList.contains('btn-group-vertical');
let nextIndex = currentIndex;
switch (event.key) {
case 'ArrowLeft':
case 'ArrowUp':
if ((event.key === 'ArrowLeft' && !isVertical) ||
(event.key === 'ArrowUp' && isVertical)) {
nextIndex = currentIndex > 0 ? currentIndex - 1 : buttons.length - 1;
event.preventDefault();
}
break;
case 'ArrowRight':
case 'ArrowDown':
if ((event.key === 'ArrowRight' && !isVertical) ||
(event.key === 'ArrowDown' && isVertical)) {
nextIndex = currentIndex < buttons.length - 1 ? currentIndex + 1 : 0;
event.preventDefault();
}
break;
case 'Home':
nextIndex = 0;
event.preventDefault();
break;
case 'End':
nextIndex = buttons.length - 1;
event.preventDefault();
break;
}
if (nextIndex !== currentIndex) {
buttons[nextIndex].focus();
}
}
// Register callback for button actions
onButtonAction(buttonId, callback) {
this.callbacks.set(buttonId, callback);
}
// Execute registered callback
executeCallback(buttonId, button) {
const callback = this.callbacks.get(buttonId);
if (callback) {
callback(button, this.getGroupState(button));
}
}
// Get current state of button group
getGroupState(button) {
const group = button.closest('.btn-group, .btn-group-vertical');
const buttons = group.querySelectorAll('button');
const activeButtons = group.querySelectorAll('button.active');
return {
totalButtons: buttons.length,
activeCount: activeButtons.length,
activeButtons: Array.from(activeButtons).map(btn => btn.dataset.buttonId),
groupType: group.dataset.groupType || 'single'
};
}
// Programmatically set button state
setButtonState(buttonId, active = true) {
const button = this.container.querySelector(`[data-button-id="${buttonId}"]`);
if (button) {
button.classList.toggle('active', active);
button.setAttribute('aria-pressed', active.toString());
}
}
// Add loading state to button
setButtonLoading(buttonId, loading = true) {
const button = this.container.querySelector(`[data-button-id="${buttonId}"]`);
if (button) {
if (loading) {
button.disabled = true;
button.innerHTML = '<i class="ri-loader-4-line me-1"></i>Loading...';
} else {
button.disabled = false;
// Restore original content - you'd store this separately
}
}
}
}
Accessibility Considerations
Ensure button groups are accessible to all users by following these best practices:
-
Use
role="group"
on button groups to indicate their relationship -
Add descriptive
aria-label
attributes to explain the button group's purpose -
Mark active states with
aria-current="page"
oraria-pressed="true"
- Ensure sufficient color contrast between button text and backgrounds
- Provide visible focus indicators for keyboard navigation
- Include descriptive text or aria-labels for icon-only buttons
On this page