Button Group

Demo

Animal

				
					
<h1>Button Group Example</h1>
<!-- We give this <div> a role of "group". Typically, screen readers will let users know when they enter and leave a group, which helps users understand when controls are related.
-->
<!-- 
What do these attributes mean?
  - role: provides an accessible role to the element. "group" sigifies a grouping related content or user controls.
  - aria-labelledby: provides an accessible name for this element. In regards to elements with a "group" role, the accessible name will typically be read by AT when a user enters and leaves the group.
  - data-btn-group: this is used to signify that this is a button group component, used in the javascript.
  - aria-pressed: this is a state. When true the element is "pressed". Similar to a radio button's checked state.
--> 
<div role="group" aria-labelledby="group-legend-animal" data-btn-group>
  <h2 id="group-legend-animal">Animal</h2>
  <div class="inner-group">
    <button aria-pressed="false">Cat</button>
    <button aria-pressed="false">Dog</button>
    <button aria-pressed="false">Frog</button>
    <button aria-pressed="false">Rat</button>
    <button aria-pressed="false">Hamster</button>
  </div>
</div>
				
			
				
					
:root {
  --bg-color: #EFEFEF;
  --color: #222;
  --border-width: max(0.15rem, 1px);
  --radius: 0.35rem;
  --padding: 0.8rem;
}

button {
  padding: 0.5rem 0.7rem;
  border: var(--border-width) solid #666;
  border-radius: var(--radius);
  background-color: var(--bg-color);
  cursor: pointer;
}

/* Set the pressed button visual styling.
 * 1.4.11 Non-text Content and 1.4.1 Use of Color are 
 * relvant success criteria.
 * Essentially, we need to make sure that the different
 * states (pressed versus not pressed) are visually
 * perceptible. This state is programmatically perceptible
 * by adding the aria-pressed attribute as noted in the HTML.
 */
button[aria-pressed="true"] {
  color: var(--bg-color);
  background-color: var(--color);
  border-color: black;
}

/* We override the default focus indicator by setting our
 * own outline.
 */
button:focus-visible {
  outline: calc(2 * var(--border-width)) solid var(--color);
  outline-offset: var(--border-width);
}

/* We provide a minor visual indication that something
 * is interactive by adding a slight box shadow.
 */
button:not(:active):hover {
  box-shadow: 0rem 0.1rem 0.25rem -0.1rem black;
}

/* We provide a minor visual indication that something
 * is interactive by slightly changing the background color
 * on hover.
 */
button[aria-pressed="false"]:hover {
  background-color: white;
}

button[aria-pressed="true"]:hover {
  background-color: #444;
}

/* Gives the button a "pressed" feel when it is pressed */
button:active {
  scale: 0.95;
}

/* Flex with flex-wrap allows the button group to wrap 
 * if the viewport is small. The 1.4.4 Text Resizing 
 * 1.4.10 Reflow Success Criteria are relevant here.
 *
 * Gap provides spacing between each button. Without it the
 * focus indicator may overlap with other buttons.
 */
.inner-group {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--border-width) * 6);
}

/* This visually styles the heading to look similar
 * to a default <fieldst> <legend> element.
 * With this styling, 1.3.1 Info and Relationships is
 * a relevant success criterion as the visual 
 * presentation indicates that these controls are
 * related, and that relationship should be
 * programmatically determinable; this is addressed
 * by adding the role=group and aria-labelledby
 * attributes in the HTML.
 */
[data-btn-group] {
  position: relative;
  border: var(--border-width) solid var(--color);
  border-radius: var(--radius);
  margin: 1.5rem 1rem;
  padding: var(--padding);
  padding-top: calc(var(--padding) + 0.5rem);
  width: fit-content;
}

[data-btn-group] :is(h1, h2, h3, h4, h5, h6) {
  position: absolute;
  top: calc(-1 * 0.5rem);
  background-color: white;
  margin: 0;
  padding: 0 0.25rem;
  font-size: 1.3rem;
  line-height: 1rem;
}
				
			
				
					
// we get all data-btn-group widget/components
let btnGroups = document.querySelectorAll('[data-btn-group]');
// for each data-btn-group, we're going to add event listeners 
btnGroups.forEach(group => {
  // we get all the buttons in the current button group
  let btns = document.querySelectorAll('.inner-group button[aria-pressed]');
  btns.forEach(btn => {
    btn.addEventListener('click', (e) => {
      // remove any other buttons pressed state
      btns.forEach(otherBtn => otherBtn.setAttribute('aria-pressed', 'false'));
      // set the current button pressed state to true
      btn.setAttribute('aria-pressed', 'true');
    });
  });
});
				
			

Why use a button group?

Button groups offer a visually cohesive and organized way to present a set of related actions or options. By grouping buttons together, they provide a clear visual hierarchy that guides users to select from a defined set of choices, making the interface more user-friendly and intuitive. Button groups are especially useful when dealing with tasks like filtering content, navigating between views, or selecting preferences. They promote consistency in design and layout, enhance user engagement by making options readily accessible, and contribute to a more efficient and streamlined user experience. Additionally, button groups can be responsive, adapting to different screen sizes, and they are customizable, allowing designers to tailor their appearance to align with the overall aesthetics of the website or application.

What are some accessibility concerns?

  • Keyboard Navigation:
    • Ensure that all buttons within the group are keyboard accessible. Users should be able to navigate and select buttons using the Tab key, and the currently focused button should be visually distinguishable.
  • Focus Management:
    • When a button is selected, manage focus appropriately within the button group. Set focus to the selected button or its associated content, ensuring that users can easily navigate through the group.
  • Semantic Markup:
    • Use semantic HTML elements (e.g., <button> or <a>) to provide meaning to the buttons within the group. This helps screen readers understand the purpose and action associated with each button.
  • Button Labels:
    • Ensure that button labels are descriptive and convey the purpose of each button. Avoid vague labels like “click here” and provide clear, concise text that explains the action.
  • Color and Contrast:
    • Be mindful of color choices and contrast within the button group. Ensure that text and button backgrounds have sufficient contrast for users with low vision or color blindness.
  • Active and Disabled States:
    • Clearly indicate when a button is active, selected, or disabled, using visual cues and ARIA roles/attributes, to help users understand the state of each button within the group.
  • Alternative Content:
    • If the button group interacts with dynamic content changes or filters, provide alternative ways for users to access and understand these changes, especially for those who rely on screen readers.
Tested using
Chrome
with NVDA
Firefox
with NVDA
  • W3C SVG
    1.4.1
    Use of Color ( level A ) :
    Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element.
  • W3C SVG
    2.4.3
    Focus Order ( level A ) :
    If a Web page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components receive focus in an order that preserves meaning and operability.
  • W3C SVG
    2.4.6
    Headings and Labels ( level AA ) :
    Headings and labels describe topic or purpose.
  • W3C SVG
    4.1.2
    Name, Role, Value ( level A ) :
    For all user interface components (including but not limited to: form elements, links and components generated by scripts), the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to user agents, including assistive technologies.