Bootstrap 5 Tutorial
v5.3.0Bootstrap 5 Tutorial
Accessibility in Bootstrap 5
Why Accessibility Matters
Accessibility ensures that people with disabilities can perceive, understand, navigate, and interact with your website. Bootstrap 5 includes many accessibility features, but proper implementation is key.
Visual
Color blindness, low vision, blindness
Auditory
Deafness, hearing impairment
Motor
Limited mobility, tremors
Cognitive
Learning disabilities, memory issues
WCAG and ARIA in Bootstrap
Accessibility Standards Support
WCAG 2.1 Compliance
Bootstrap aims to meet WCAG 2.1 AA standards out of the box.
| Principle | Bootstrap Feature |
|---|---|
| Perceivable | Color contrast, text alternatives, adaptable content |
| Operable | Keyboard navigation, focus indicators, sufficient time |
| Understandable | Predictable navigation, input assistance, readable text |
| Robust | Compatible with assistive technologies, valid HTML |
ARIA Attributes in Bootstrap
Bootstrap includes ARIA attributes for improved screen reader support.
<!-- Bootstrap adds ARIA attributes automatically -->
<!-- Modal with ARIA -->
<div class="modal" tabindex="-1" aria-labelledby="modalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalLabel">Modal title</h5>
</div>
</div>
</div>
</div>
<!-- Alert with role -->
<div class="alert alert-success" role="alert">
Success alert with ARIA role
</div>
<!-- Navigation with ARIA -->
<nav class="navbar" aria-label="Main navigation">
<!-- Navigation content -->
</nav>
<!-- Progress bar -->
<div class="progress">
<div class="progress-bar"
role="progressbar"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100">
75%
</div>
</div>
<!-- Carousel with ARIA -->
<div id="carouselExample" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-inner" role="listbox">
<!-- Slides with aria-label -->
</div>
</div>Key ARIA Roles in Bootstrap:
role="alert"- For alert componentsrole="dialog"- For modal dialogsrole="navigation"- For navigation elementsrole="progressbar"- For progress indicatorsrole="tablist"- For tab componentsrole="button"- For custom button elementsrole="search"- For search forms
Color Contrast and Visual Accessibility
Color Accessibility
WCAG Contrast Requirements
| Level | Normal Text | Large Text | UI Components |
|---|---|---|---|
| AA (Minimum) | 4.5:1 | 3:1 | 3:1 |
| AAA (Enhanced) | 7:1 | 4.5:1 | 3:1 |
Bootstrap's Default Contrast
Testing Color Contrast
/* Bootstrap's color contrast function */
// In Sass, Bootstrap uses color-contrast()
@function color-contrast($background) {
// Returns white or black based on contrast
}
// Customizing for better accessibility
// Override Bootstrap colors for better contrast
$primary: #0056b3; // Darker blue for better contrast
$success: #0a5c36; // Darker green
$danger: #9c1a1a; // Darker red
// Or use CSS custom properties
:root {
--bs-primary: #0056b3;
--bs-primary-rgb: 0, 86, 179;
}
/* Checking contrast manually */
// Use online tools or browser extensions:
// - WebAIM Contrast Checker
// - axe DevTools
// - Chrome Lighthouse
// - Color Contrast Analyzer
/* CSS for high contrast mode */
@media (prefers-contrast: high) {
:root {
--bs-body-color: #000;
--bs-body-bg: #fff;
--bs-border-color: #000;
}
.btn-primary {
background-color: #000;
color: #fff;
border: 2px solid #000;
}
}
/* Supporting Windows High Contrast Mode */
@media screen and (-ms-high-contrast: active) {
/* High contrast specific styles */
.btn {
border: 2px solid currentColor;
}
img {
border: 1px solid currentColor;
}
}Color Blindness Considerations
❌ Color-Only Indicators
Hard to distinguish for color blind users
✅ Enhanced Indicators
Icons + color provide multiple cues
✅ Text + Patterns
Clear text labels with visual patterns
Keyboard Navigation
Keyboard Accessibility
All interactive elements should be operable via keyboard. Bootstrap components include keyboard support by default.
Focus Management
/* Focus styles in Bootstrap */
.btn:focus {
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.form-control:focus {
border-color: #86b7fe;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
/* Custom focus styles for better visibility */
.custom-element:focus {
outline: 3px solid #ffbf47; /* High visibility yellow */
outline-offset: 2px;
}
/* Never remove focus outlines completely */
/* ❌ Bad practice */
*:focus {
outline: none;
}
/* ✅ Better practice */
*:focus:not(:focus-visible) {
outline: none;
}
*:focus-visible {
outline: 3px solid #005a9c;
outline-offset: 2px;
}
/* Focus order should follow visual order */
/* Use tabindex carefully */
<div tabindex="0">Focusable div</div>
<button tabindex="-1">Not tabbable but focusable via JS</button>
/* Skip links for keyboard users */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: white;
padding: 8px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
/* Modal focus trapping */
// Bootstrap modals trap focus automatically
// When modal opens, focus moves to modal
// Tab key cycles through modal elements only
// Shift+Tab works in reverse
// Escape key closes modalKeyboard Shortcuts & Navigation
| Component | Keyboard Support |
|---|---|
| Modal | ESC closes, Tab cycles, Enter activates buttons |
| Dropdown | Enter/Space opens, Arrow keys navigate, ESC closes |
| Carousel | Arrow keys navigate, Home/End to first/last |
| Tab Panels | Arrow keys navigate, Enter/Space activates |
| Alert | Dismissible with Close button (keyboard accessible) |
| Navigation | Tab through links, Enter activates |
| Forms | Tab through fields, Enter submits |
Testing Keyboard Navigation
Keyboard Testing Checklist:
- ✅ All interactive elements reachable via Tab key
- ✅ Focus order follows visual layout
- ✅ Focus indicators clearly visible
- ✅ No keyboard traps (user can Tab out)
- ✅ Complex widgets have keyboard instructions
- ✅ Skip links available for bypassing navigation
- ✅ Form controls properly labeled
- ✅ Custom controls have appropriate roles
Screen Reader Support
Screen Reader Accessibility
ARIA Labels and Descriptions
<!-- Proper labeling -->
<!-- Explicit labels -->
<label for="email">Email address</label>
<input type="email" id="email" class="form-control">
<!-- ARIA labels -->
<button class="btn btn-primary" aria-label="Close dialog">
<i class="bi bi-x"></i>
</button>
<!-- ARIA describedby -->
<input type="password"
class="form-control"
aria-describedby="passwordHelp">
<div id="passwordHelp" class="form-text">
Password must be at least 8 characters.
</div>
<!-- ARIA live regions for dynamic content -->
<div aria-live="polite" aria-atomic="true">
<!-- Dynamic content updates read automatically -->
</div>
<div aria-live="assertive" aria-atomic="true">
<!-- Important alerts read immediately -->
</div>
<!-- Hide decorative elements -->
<button class="btn btn-primary">
<i class="bi bi-search" aria-hidden="true"></i>
Search
</button>
<!-- Hide from screen readers (carefully!) -->
<div aria-hidden="true">
<!-- Purely decorative content -->
</div>
<!-- Screen reader only text -->
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
<button class="btn btn-primary">
<span class="visually-hidden">Search</span>
<i class="bi bi-search"></i>
</button>
<!-- Landmark roles -->
<header role="banner">
<!-- Page header -->
</header>
<nav role="navigation" aria-label="Main">
<!-- Main navigation -->
</nav>
<main role="main">
<!-- Main content -->
</main>
<aside role="complementary">
<!-- Sidebar content -->
</aside>
<footer role="contentinfo">
<!-- Footer content -->
</footer>Screen Reader Testing
Common Screen Reader Issues:
- Missing labels: Form inputs without associated labels
- Empty links/buttons: Icons without text alternatives
- Poor heading structure: Skipping heading levels (h1 → h3)
- Insufficient descriptions: Complex elements without explanations
- Dynamic content: Updates not announced to screen readers
- ARIA misuse: Incorrect or redundant ARIA attributes
Testing with Screen Readers
| Screen Reader | Browser | OS |
|---|---|---|
| NVDA | Firefox | Windows |
| JAWS | Chrome, Firefox | Windows |
| VoiceOver | Safari | macOS, iOS |
| TalkBack | Chrome | Android |
| Narrator | Edge | Windows |
Headings Structure Example
Page Title (h1)
Section Heading (h2)
Subsection (h3)
Another Section (h2)
✅ Logical heading hierarchy
Forms Accessibility
Accessible Forms
Proper Form Structure
Form Validation Accessibility
<!-- Accessible form validation -->
<form novalidate>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email"
class="form-control"
id="email"
aria-describedby="emailError"
aria-invalid="false"
required>
<!-- Error message (initially hidden) -->
<div id="emailError" class="invalid-feedback visually-hidden">
Please enter a valid email address.
</div>
</div>
</form>
<!-- JavaScript for accessible validation -->
document.querySelector('form').addEventListener('submit', function(e) {
e.preventDefault();
const emailInput = document.getElementById('email');
const errorElement = document.getElementById('emailError');
if (!emailInput.validity.valid) {
// Show error
emailInput.classList.add('is-invalid');
errorElement.classList.remove('visually-hidden');
// Set ARIA attributes
emailInput.setAttribute('aria-invalid', 'true');
emailInput.setAttribute('aria-describedby', 'emailError');
// Focus on invalid field
emailInput.focus();
} else {
// Clear error
emailInput.classList.remove('is-invalid');
errorElement.classList.add('visually-hidden');
emailInput.setAttribute('aria-invalid', 'false');
}
});
/* CSS for validation states */
.is-invalid {
border-color: #dc3545;
padding-right: calc(1.5em + 0.75rem);
background-image: url("data:image/svg+xml,...");
background-repeat: no-repeat;
background-position: right calc(0.375em + 0.1875rem) center;
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
}
.is-invalid:focus {
border-color: #dc3545;
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}
.invalid-feedback {
display: block;
width: 100%;
margin-top: 0.25rem;
font-size: 0.875em;
color: #dc3545;
}
/* Required field indicators */
.required::after {
content: " *";
color: #dc3545;
}
/* Grouping related fields */
<fieldset>
<legend>Contact Information</legend>
<!-- Form fields -->
</fieldset>Common Form Accessibility Issues:
❌ Inaccessible Form Patterns:
- Missing or poorly associated labels
- Placeholder text as only label
- No error identification or descriptions
- Insufficient time limits without warning
- CAPTCHA without audio alternative
- Custom controls without keyboard support
✅ Accessible Form Patterns:
- Explicit
<label>elements for all inputs - Clear error messages with
aria-describedby - Logical tab order matching visual layout
- Sufficient time limits with extend option
- Accessible CAPTCHA alternatives
- Custom controls with proper ARIA roles
Component-Specific Accessibility
Accessible Bootstrap Components
Modal Accessibility
<!-- Accessible modal -->
<div class="modal fade"
id="exampleModal"
tabindex="-1"
aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">
Modal title
</h5>
<button type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
</div>
</div>
</div>
<!-- Bootstrap handles:
- Focus trapping
- ESC key to close
- aria-hidden on background
- Return focus to triggerDropdown Accessibility
<!-- Accessible dropdown -->
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownMenuButton"
data-bs-toggle="dropdown"
aria-expanded="false">
Dropdown button
</button>
<ul class="dropdown-menu"
aria-labelledby="dropdownMenuButton">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
</ul>
</div>
<!-- Bootstrap handles:
- ARIA expanded state
- Keyboard navigation (arrows)
- ESC to close
- Focus managementCarousel Accessibility
<!-- Accessible carousel -->
<div id="carouselExample"
class="carousel slide"
data-bs-ride="carousel">
<div class="carousel-inner" role="listbox">
<div class="carousel-item active">
<img src="..." class="d-block w-100" alt="First slide">
</div>
</div>
<button class="carousel-control-prev" ...>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" ...>
<span class="visually-hidden">Next</span>
</button>
</div>
<!-- Provide:
- Alt text for images
- ARIA labels for controls
- Pause/play buttons
- Status region for current slideNavigation Accessibility
<!-- Accessible navbar -->
<nav class="navbar navbar-expand-lg navbar-light bg-light"
aria-label="Main navigation">
<div class="container-fluid">
<a class="navbar-brand" href="#">Site Name</a>
<button class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active"
aria-current="page"
href="#">
Home
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Features</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- Breadcrumb accessibility -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="#">Home</a></li>
<li class="breadcrumb-item"><a href="#">Library</a></li>
<li class="breadcrumb-item active" aria-current="page">
Data
</li>
</ol>
</nav><!-- Tab component accessibility -->
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active"
id="home-tab"
data-bs-toggle="tab"
data-bs-target="#home"
type="button"
role="tab"
aria-controls="home"
aria-selected="true">
Home
</button>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active"
id="home"
role="tabpanel"
aria-labelledby="home-tab">
Tab content
</div>
</div>
<!-- Alert accessibility -->
<div class="alert alert-success alert-dismissible fade show"
role="alert">
<strong>Success!</strong> Operation completed.
<button type="button"
class="btn-close"
data-bs-dismiss="alert"
aria-label="Close"></button>
</div>
<!-- Progress bar accessibility -->
<div class="progress">
<div class="progress-bar"
role="progressbar"
style="width: 75%"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100">
<span class="visually-hidden">75% complete</span>
</div>
</div>Testing and Validation
Automated Testing Tools
| Tool | Type | Best For |
|---|---|---|
| axe DevTools | Browser Extension | Comprehensive testing, WCAG compliance |
| Lighthouse | Browser DevTools | Quick audits, performance + accessibility |
| WAVE | Browser Extension/Online | Visual feedback, easy to understand |
| HTML CodeSniffer | Bookmarklet | Standards compliance (WCAG, Section 508) |
| Color Contrast Analyzer | Browser Extension | Color contrast testing |
Manual Testing Checklist
- Keyboard navigation: Tab through entire page
- Screen reader testing: NVDA, JAWS, VoiceOver
- Zoom testing: 200% zoom, text resizing
- Color blindness simulation: Use browser tools
- High contrast mode: Windows/Mac high contrast
- Mobile accessibility: Touch target sizes, gestures
- Form validation: Error messages, required fields
- Media accessibility: Captions, transcripts, audio descriptions
- Performance testing: Load times with assistive tech
Quick Manual Tests:
- Unplug mouse - navigate with keyboard only
- Turn on screen reader - listen to page structure
- Zoom to 200% - check layout and readability
- Check color contrast - use online checker
- Test forms - submit with validation errors
- Check images - all have alt text
- Verify headings - logical hierarchy (h1 → h2 → h3)
- Test modals/dropdowns - keyboard accessible
Common Accessibility Violations in Bootstrap
| Violation | WCAG Criteria | Solution |
|---|---|---|
| Low color contrast | 1.4.3 Contrast (Minimum) | Use Bootstrap's contrast functions, test with tools |
| Missing form labels | 1.3.1 Info and Relationships | Always use <label> elements |
| Missing alt text on images | 1.1.1 Non-text Content | Add descriptive alt attributes |
| Insufficient focus indicators | 2.4.7 Focus Visible | Ensure focus styles are visible |
| Improper heading structure | 1.3.1 Info and Relationships | Use proper heading hierarchy |
| Missing ARIA attributes | 4.1.2 Name, Role, Value | Add appropriate ARIA roles/labels |
| Keyboard traps | 2.1.2 No Keyboard Trap | Ensure all components can be exited with keyboard |
| Missing error identification | 3.3.1 Error Identification | Clearly identify and describe form errors |
Best Practices Summary
Accessibility Best Practices with Bootstrap
- Use semantic HTML: Proper elements for proper purposes
- Ensure keyboard navigation: All interactive elements should be keyboard accessible
- Provide text alternatives: Alt text for images, transcripts for audio/video
- Maintain sufficient contrast: Meet WCAG 2.1 AA contrast ratios
- Create clear focus indicators: Visible focus states for keyboard users
- Use ARIA appropriately: Enhance, don't replace, native semantics
- Test with assistive technology: Screen readers, keyboard navigation, zoom
- Follow heading hierarchy: Logical document structure (h1 → h2 → h3)
- Design accessible forms: Labels, error messages, clear instructions
- Consider cognitive accessibility: Clear language, consistent navigation
- Plan for responsive accessibility: Accessible on all screen sizes
- Document accessibility features: Team should understand implementation
Quick Accessibility Checklist
Remember: Accessibility is a Process
Accessibility isn't a one-time checkbox. It's an ongoing process that should be integrated into your development workflow. Start with the basics, test regularly, involve people with disabilities in testing when possible, and continuously improve. Bootstrap gives you a great foundation, but you must implement it accessibly.