Module 26 : Buttons & Button Groups.
1 What is a button? (UX & semantics)
Semantic element: Use <button> for actions. <a> for navigation (with role="button" only in rare progressive-enhancement cases).
Affordance: A button suggests “click/press”. Visual cues (shape, shadow, contrast) indicate interactivity.
Hierarchy & purpose: Primary (single main action), Secondary (less emphasis), Tertiary (low emphasis), Danger, Link-style, Icon-only.
Microcopy: Button label should be short, action-focused: "Save", "Add to cart", "Upload file" (verb-first).
Interaction states
Default — normal appearance.
Hover — pointer hovering (desktop).
Focus — keyboard focus: must be visible (outline, ring).
Active / Pressed — when mouse or touch is pressed.
Disabled — non-interactive. Use disabled attribute for <button>.
Loading — action in progress: show spinner and (optionally) aria-busy.
principles
Fitts’ Law: Larger, closer targets are easier/faster to hit. Increase touch targets on mobile.
Hick’s Law: Too many choices slow decision time — group actions and emphasize the most important.
Consistency: Keep button placement & style consistent across the app.
Accessibility fundamentals
Use <button> (automatic keyboard support).
When using non-button elements to act as buttons, ensure role="button", keyboard handlers (Enter & Space), and ARIA where needed.
Provide clear labels, avoid ambiguous icons without visible text unless tooltip or aria-label provided.
Focus styling must be visible and maintain color contrast (WCAG).
For groups, use role="toolbar" or aria-pressed for toggle buttons.
2 Methods
Start semantic: <button>, type="button|submit|reset".
Base styles: create a small design system of CSS variables for colors, spacing, radii, and use them.
States: style :hover, :focus, :active, [disabled], and create a CSS class for .is-loading.
Variants: .btn-primary, .btn-secondary, .btn-icon, .btn-ghost.
Button groups: horizontal groups with flexible layout, segmented controls with toggling.
Compound components: icon + label, label + badge (count), split buttons (drop down + main action).
Responsive & touch: ensure min target size (44x44px common mobile target).
Testing: keyboard-only navigation, screen reader labels, automated axe checks.
3 Examples
3.1 Minimal semantic HTML + CSS (vanilla)
<!-- index.html --> <button class="btn btn-primary" type="button">Save</button> <button class="btn btn-secondary" type="button" disabled>Cancel</button> <button class="btn btn-ghost" type="button" aria-label="More options"> ⋯ </button>
/* styles.css - component-focused, uses CSS custom properties */ :root{ --btn-padding: 0.5rem 1rem; --btn-radius: 8px; --primary-bg: #0b5cff; --primary-fg: #fff; --secondary-bg: #f2f4f7; --secondary-fg: #111827; --disabled-opacity: 0.5; --focus-ring: 0 0 0 4px rgba(11,92,255,0.15); --btn-font-size: 0.95rem; } .btn{ display: inline-flex; align-items: center; gap: 0.5rem; padding: var(--btn-padding); border-radius: var(--btn-radius); border: 1px solid transparent; font-size: var(--btn-font-size); cursor: pointer; user-select: none; transition: transform .06s ease, box-shadow .12s ease; background: var(--secondary-bg); color: var(--secondary-fg); } .btn:focus{ outline: none; box-shadow: var(--focus-ring); } .btn:active{ transform: translateY(1px); } .btn[disabled]{ cursor: not-allowed; opacity: var(--disabled-opacity); transform: none; } /* Variants */ .btn-primary{ background: var(--primary-bg); color: var(--primary-fg); border-color: rgba(0,0,0,0.05); } .btn-primary:hover{ filter: brightness(.96); } .btn-ghost{ background: transparent; color: var(--secondary-fg); border-color: transparent; } /* Icon-only */ .btn.icon{ padding: 0.5rem; width: 40px; height: 40px; justify-content: center; }
Uses CSS variables to make theming easy.
:focus has a visible ring for keyboard users.
Disabled uses attribute selector for reliable behavior.
display: inline-flex centers icon+text.
3.2 — Button group (segmented control)
<div class="btn-group" role="toolbar" aria-label="Text alignment"> <button class="btn btn-toggle" aria-pressed="true" title="Align left">L</button> <button class="btn btn-toggle" aria-pressed="false" title="Center">C</button> <button class="btn btn-toggle" aria-pressed="false" title="Align right">R</button> </div>
.btn-group{ display: inline-flex; border-radius: 8px; overflow: hidden; border: 1px solid rgba(0,0,0,0.06); } .btn-group .btn-toggle{ border-radius: 0; padding: 0.45rem .8rem; background: white; } .btn-toggle[aria-pressed="true"]{ background: var(--primary-bg); color: var(--primary-fg); }
Notes:
aria-pressed communicates toggle state to tech.
Group uses overflow: hidden so adjacent buttons appear joined.
3.3 — Loading button pattern (JS)
<button id="saveBtn" class="btn btn-primary"> <span class="btn-label">Save</span> <span class="spinner" aria-hidden="true" hidden>⏳</span> </button> <script> const saveBtn = document.getElementById('saveBtn'); saveBtn.addEventListener('click', async () => { saveBtn.disabled = true; saveBtn.querySelector('.btn-label').textContent = 'Saving...'; const spinner = saveBtn.querySelector('.spinner'); spinner.hidden = false; try{ // simulate network await new Promise(r => setTimeout(r, 1200)); } finally { spinner.hidden = true; saveBtn.disabled = false; saveBtn.querySelector('.btn-label').textContent = 'Save'; } }); </script>
Accessibility tip: On long operations, use aria-busy="true" on an ancestor or aria-live region to announce state changes if needed.
3.4 React Button component (production-quality)
// Button.jsx (default export) import React from "react"; /** * Props: * - variant: 'primary'|'secondary'|'ghost' * - size: 'sm'|'md'|'lg' * - loading: boolean * - disabled: boolean * - onClick: function * - children */ export default function Button({ variant = "primary", size = "md", loading = false, disabled = false, type = "button", ...rest }) { const isDisabled = disabled || loading; const className = [ "btn", `btn-${variant}`, `btn-size-${size}`, loading ? "is-loading": "" ].join(" "); return ( <button {...rest} type={type} className={className} disabled={isDisabled} aria-busy={loading || undefined} > {loading && <span className="btn-spinner" aria-hidden="true"></span>} <span className="btn-label">{rest.children}</span> </button> ); }
Specialist notes:
Don’t forward event handlers if disabled (some libs block them).
Use aria-busy to signal loading; avoid removing the button entirely (preserve layout).
Keep CSS modular (CSS, Tailwind, or utility classes) for predictable styling.
4 Exercises
Exercise 1 — Build 3 button variants
Create Primary, Secondary, and Ghost buttons using CSS variables. Include :hover and :focus.
What to check: Focus ring visible; hover changes; disabled state grayed out.
point: Theming via CSS variables simplifies switching palettes.
Exercise 2 — Create a segmented toggle group
Build a horizontal toggle group (3 options) that uses aria-pressed and keyboard navigation (Left/Right arrows to switch). Implement in JS.
Keep a focusedIndex, listen for keydown and update aria-pressed. Use role="toolbar".
Expected result: Arrow keys move pressed state; screen reader announces buttons’ pressed/unpressed.
Exercise 3 — Accessible icon-only button
Create an icon-only button for “Close” with visually-hidden text and aria-label.
Check: The label is read by a screen reader and the focus ring is visible.
Exercise 4 — Split button with dropdown
Implement a split button: left is the main action, right toggles a dropdown. The right side opens a small menu. Ensure the menu is keyboard accessible.
point: Compound controls introduce focus management — consider roving tabindex or aria-controls.
5 Accessibility
Measure and compare the speed and accuracy of three button designs for a single action across desktop & mobile: (A) small flat button, (B) large primary with clear label and icon, (C) compact icon-only with tooltip.
Setup
Create a small web page with three buttons representing the same action (e.g., “Add item”).
On desktop: click the correct button when a prompt appears.
On mobile: tap the correct button.
Recruit (mixed desktop/mobile). Record:
Time-to-click/tap (ms).
Misses / accidental taps (clicks outside or on wrong button).
Subjective rating of perceived clarity and confidence.
Run automated accessibility checks (axe-core) and manual keyboard testing:
Tab order, focus ring visibility, aria labels, screen reader announcement.
Hypothesis
The large primary button (B) will have the fastest average time and lowest error rate ().
Icon-only (C) will have more errors unless the icon is widely recognized.
Measurements & analysis
Compute mean & standard deviation of times. Use t-test to compare A vs B (if you know stats) or simple visual comparisons.
Report hits/misses as percentages.
Combine objective metrics with qualitative feedback (participants’ comments).
deliverables
CSV of raw timings and outcomes.
Summary report: mean time, error rate, quotes.
Recommendations: preferred design for the product context.
6 — Research
target size & spacing.
Hick’s Law — decision complexity vs reaction time.
Norman, D. — The Design of Everyday Things (affordance, feedback).
Accessibility & Standards
WCAG 2.1 (focus contrast, target size guidance) — implementable rules.
ARIA Authoring patterns for toolbar, button, togglebutton.
HCI & factors
Research papers on touch target sizes (mobile), menu & button discoverability.
Axe-core (Deque) — automated accessibility testing.
WAI-ARIA patterns (W3C) — behavior patterns for custom controls.
7 checklist
All interactive elements are <button> or have role="button" + keyboard handlers.
The focus ring is visible and meets contrast.
Keyboard navigation: Tab to button, Enter/Space activates, arrow keys manage groups if applicable.
aria-pressed for toggle buttons; aria-expanded for menus.
Disabled states use disabled attributes.
Loading states use aria-busy or aria-live feedback for long ops.
Touch target >= 44x44px (or product team’s standard).
Use automated scans (axe) and at least one screen reader test (NVDA/VoiceOver).
Cross-browser visual review and mobile testing (Safari iOS, Chrome Android).
8 Grading
Correctness (40%) — semantic HTML, correct attributes, functioning JS.
Accessibility (25%) — keyboard support, screen reader labels, focus styles.
Design & Usability (20%) — visual hierarchy, spacing, responsiveness.
Code quality (15%) — CSS, clear naming, comments, reusable components.
9 Tips
CSS architecture: Use BEM, CSS or Tailwind; keep button atomic (.btn) and variants separate.
Theming: Expose CSS variables for color, radius, spacing; allows runtime theme switches.
Bundle size: Button components should be small. Avoid shipping icon fonts — use inline SVGs or optimized sprite.
Performance: Avoid expensive box-shadow for hundreds of buttons; prefer subtle borders or 2D transforms.
Testing: Snapshot tests for visual regressions; component tests for state changes; axe for accessibility.
Internationalization: Buttons with dynamic labels should not be truncated; allow for longer translations.
ARIA caution: Do not overuse ARIA — prefer native elements. Only add ARIA when no semantic option exists.
Animation: Keep them short (<200ms). Respect prefers-reduced-motion.
10 advanced topics
Implement a roving tabindex for complex keyboard-managed groups.
Build a design tokens system so button tokens sync with product palette.
Implement server-side fallback for critical actions (idempotency) when buttons trigger network operations.
Test button click heatmaps in product analytics (event instrumentation).
11 reference snippet: Accessible toggle-button keyboard behavior (pseudo)
Left/Right arrows: move logical selection for radio-like segments.
Space/Enter: activate the pressed button.
Tab: move out of the group, focusing on the next focusable item.
Use role="toolbar" or role="group" to clarify structure; include aria-label.
Suggest
Use the plan as a one-off session or split into two (basics + advanced).
Pair the participants where possible — the learning comes from observing users.
submit a small button library repository as the final project: HTML/CSS/JS + React component + tests + README.
No comments:
Post a Comment