Visibility and Interactions
Bootstrap provides utility classes for controlling element visibility, user interactions, and responsive embeds. These utilities help you create accessible and interactive user interfaces.
Visibility Utilities
Control element visibility without affecting layout:
<!-- Visible and Invisible -->
<div class="visible">
This element is visible (default)
</div>
<div class="invisible">
This element is invisible but still takes up space in layout
</div>
<!-- Comparison with d-none -->
<div class="d-none">
This element is hidden and does NOT take up space (display: none)
</div>
<div class="invisible">
This element is hidden but DOES take up space (visibility: hidden)
</div>
<!-- Practical Example: Toggle Visibility -->
<button class="btn btn-primary" onclick="toggleVisibility()">
Toggle Element
</button>
<div id="toggleElement" class="mt-3 p-3 bg-light">
This element can be toggled
</div>
<script>
function toggleVisibility() {
const element = document.getElementById('toggleElement');
element.classList.toggle('invisible');
}
</script>
Note: Use invisible when you want to hide an element but preserve its space in the layout. Use d-none when you want to completely remove it from the layout.
Screen Reader Utilities
Create accessible interfaces by hiding content visually while keeping it available to screen readers:
<!-- Visually Hidden (but accessible to screen readers) -->
<span class="visually-hidden">
This text is hidden visually but read by screen readers
</span>
<button class="btn btn-primary">
<i class="bi bi-search"></i>
<span class="visually-hidden">Search</span>
</button>
<!-- Visually Hidden Focusable -->
<a href="#main-content" class="visually-hidden-focusable">
Skip to main content
</a>
<!-- Icon-only Button with Screen Reader Text -->
<button class="btn btn-danger">
<i class="bi bi-trash"></i>
<span class="visually-hidden">Delete item</span>
</button>
<!-- Loading Spinner with Screen Reader Text -->
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<!-- Form Label (visually hidden) -->
<label for="search" class="visually-hidden">Search</label>
<input type="text" class="form-control" id="search" placeholder="Search...">
<!-- Skip Navigation Link -->
<header>
<a href="#main" class="visually-hidden-focusable">
Skip to main content
</a>
<nav>...</nav>
</header>
<main id="main">...</main>
Tip: Always use visually-hidden for icon-only buttons to ensure screen reader users understand the button's purpose.
Stretched Links
Make entire cards or containers clickable with stretched links:
<!-- Basic Stretched Link -->
<div class="card" style="width: 18rem;">
<img src="image.jpg" class="card-img-top" alt="Product">
<div class="card-body">
<h5 class="card-title">Card Title</h5>
<p class="card-text">Card description text.</p>
<a href="#" class="stretched-link">Go somewhere</a>
</div>
</div>
<!-- Multiple Cards with Stretched Links -->
<div class="row g-3">
<div class="col-md-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Product 1</h5>
<p class="card-text">Description</p>
<a href="#" class="stretched-link">View Details</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Product 2</h5>
<p class="card-text">Description</p>
<a href="#" class="stretched-link">View Details</a>
</div>
</div>
</div>
</div>
<!-- Stretched Link with Position Relative -->
<div class="position-relative p-3 bg-light">
<h4>Clickable Container</h4>
<p>This entire container is clickable.</p>
<a href="#" class="stretched-link">Click here</a>
</div>
<!-- Identifying Container with Position -->
<div class="card position-relative">
<div class="card-body">
<h5 class="card-title">Title</h5>
<p class="card-text">Content</p>
<a href="#" class="stretched-link">Link</a>
</div>
</div>
Warning: Stretched links only work with the nearest positioned ancestor. If the card doesn't have position-relative, the link might stretch beyond intended boundaries.
Text Selection Utilities
Control user text selection behavior:
<!-- User Select All -->
<p class="user-select-all">
Click to select all this text at once (useful for code snippets or IDs)
</p>
<!-- User Select None -->
<p class="user-select-none">
This text cannot be selected by the user
</p>
<!-- User Select Auto (default) -->
<p class="user-select-auto">
This text can be selected normally (default behavior)
</p>
<!-- Practical Examples -->
<!-- API Key Display -->
<div class="p-3 bg-light">
<label>API Key:</label>
<code class="user-select-all d-block">
sk_test_4eC39HqLyjWDarjtT1zdp7dc
</code>
</div>
<!-- Button Text (prevent selection) -->
<button class="btn btn-primary user-select-none">
Click Me
</button>
<!-- Navigation Links (prevent selection) -->
<nav class="user-select-none">
<a href="#">Home</a>
<a href="#">About</a>
<a href="#">Contact</a>
</nav>
<!-- Code Snippet (easy to copy) -->
<pre class="user-select-all bg-dark text-white p-3">
npm install bootstrap
</pre>
Tip: Use user-select-all for API keys, tokens, or code snippets to make copying easier for users.
Pointer Events
Control whether elements respond to pointer events:
<!-- Pointer Events None -->
<div class="pe-none">
<a href="#">This link is not clickable</a>
<button class="btn btn-primary">This button is not clickable</button>
</div>
<!-- Pointer Events Auto (default) -->
<div class="pe-auto">
<a href="#">This link is clickable</a>
</div>
<!-- Disabled Overlay -->
<div class="position-relative">
<div class="content">
<button class="btn btn-primary">Action</button>
</div>
<div class="position-absolute top-0 start-0 w-100 h-100 pe-none">
<!-- Transparent overlay that doesn't block clicks -->
</div>
</div>
<!-- Loading State with Disabled Interactions -->
<div class="card" id="loadingCard">
<div class="card-body pe-none opacity-50">
<h5 class="card-title">Loading...</h5>
<p class="card-text">Content</p>
<button class="btn btn-primary">Submit</button>
</div>
</div>
<!-- Selectively Enable Interactions -->
<div class="pe-none">
<p>This text is not selectable</p>
<button class="btn btn-primary pe-auto">
But this button is clickable
</button>
</div>
Note: pe-none makes an element ignore all pointer events, but descendants can override this with pe-auto.
Ratio Helpers for Responsive Embeds
Maintain consistent aspect ratios for embedded content:
<!-- 16:9 Aspect Ratio (default for videos) -->
<div class="ratio ratio-16x9">
<iframe src="https://www.youtube.com/embed/..." allowfullscreen></iframe>
</div>
<!-- 4:3 Aspect Ratio -->
<div class="ratio ratio-4x3">
<iframe src="https://www.youtube.com/embed/..." allowfullscreen></iframe>
</div>
<!-- 21:9 Aspect Ratio (ultrawide) -->
<div class="ratio ratio-21x9">
<iframe src="https://www.youtube.com/embed/..." allowfullscreen></iframe>
</div>
<!-- 1:1 Aspect Ratio (square) -->
<div class="ratio ratio-1x1">
<div class="d-flex align-items-center justify-content-center bg-light">
Square Content
</div>
</div>
<!-- Custom Aspect Ratio -->
<div class="ratio" style="--bs-aspect-ratio: 50%;">
<div>2:1 aspect ratio</div>
</div>
<!-- Practical Examples -->
<!-- Responsive Video Embed -->
<div class="container">
<div class="ratio ratio-16x9">
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="YouTube video"
allowfullscreen
></iframe>
</div>
</div>
<!-- Google Maps Embed -->
<div class="ratio ratio-4x3">
<iframe
src="https://www.google.com/maps/embed?..."
style="border:0;"
allowfullscreen=""
loading="lazy"
></iframe>
</div>
<!-- Image Gallery with Consistent Ratios -->
<div class="row g-3">
<div class="col-md-4">
<div class="ratio ratio-1x1">
<img src="image1.jpg" class="object-fit-cover" alt="Image 1">
</div>
</div>
<div class="col-md-4">
<div class="ratio ratio-1x1">
<img src="image2.jpg" class="object-fit-cover" alt="Image 2">
</div>
</div>
<div class="col-md-4">
<div class="ratio ratio-1x1">
<img src="image3.jpg" class="object-fit-cover" alt="Image 3">
</div>
</div>
</div>
Tip: Ratio helpers prevent content from jumping or reflowing as embedded media loads, improving user experience.
Object Fit Utilities
Control how images and videos fit within their containers:
<!-- Object Fit Contain -->
<img src="image.jpg" class="object-fit-contain" style="width: 300px; height: 200px;" alt="Contained">
<!-- Object Fit Cover -->
<img src="image.jpg" class="object-fit-cover" style="width: 300px; height: 200px;" alt="Covered">
<!-- Object Fit Fill -->
<img src="image.jpg" class="object-fit-fill" style="width: 300px; height: 200px;" alt="Filled">
<!-- Object Fit Scale Down -->
<img src="image.jpg" class="object-fit-scale" style="width: 300px; height: 200px;" alt="Scaled">
<!-- Object Fit None -->
<img src="image.jpg" class="object-fit-none" style="width: 300px; height: 200px;" alt="None">
<!-- Responsive Object Fit -->
<img src="image.jpg" class="object-fit-contain object-fit-md-cover" alt="Responsive">
<!-- Practical Examples -->
<!-- Product Thumbnails (consistent size) -->
<div class="row g-3">
<div class="col-4">
<img src="product1.jpg" class="object-fit-cover w-100" style="height: 200px;" alt="Product 1">
</div>
<div class="col-4">
<img src="product2.jpg" class="object-fit-cover w-100" style="height: 200px;" alt="Product 2">
</div>
<div class="col-4">
<img src="product3.jpg" class="object-fit-cover w-100" style="height: 200px;" alt="Product 3">
</div>
</div>
<!-- Avatar with Circular Crop -->
<img src="avatar.jpg" class="rounded-circle object-fit-cover" style="width: 100px; height: 100px;" alt="Avatar">
<!-- Banner Image -->
<div style="height: 400px; overflow: hidden;">
<img src="banner.jpg" class="w-100 h-100 object-fit-cover" alt="Banner">
</div>
<!-- Video Background -->
<div class="position-relative" style="height: 500px;">
<video class="position-absolute top-0 start-0 w-100 h-100 object-fit-cover" autoplay muted loop>
<source src="background.mp4" type="video/mp4">
</video>
<div class="position-relative z-1 text-white text-center">
<h1>Content over video</h1>
</div>
</div>
Note: Object fit utilities require the element to have defined dimensions (width and height) to work properly.
Cursor Utilities
Change cursor appearance on hover (requires custom CSS in Bootstrap 5):
<!-- Custom Cursor Styles -->
<style>
.cursor-pointer { cursor: pointer; }
.cursor-grab { cursor: grab; }
.cursor-grabbing { cursor: grabbing; }
.cursor-not-allowed { cursor: not-allowed; }
.cursor-wait { cursor: wait; }
.cursor-help { cursor: help; }
.cursor-crosshair { cursor: crosshair; }
.cursor-move { cursor: move; }
.cursor-text { cursor: text; }
.cursor-zoom-in { cursor: zoom-in; }
.cursor-zoom-out { cursor: zoom-out; }
</style>
<!-- Usage Examples -->
<div class="cursor-pointer p-3 bg-light">
Hover over me (pointer cursor)
</div>
<div class="cursor-not-allowed p-3 bg-light">
This action is disabled
</div>
<div class="cursor-help p-3 bg-light">
Hover for help
</div>
<div class="cursor-grab p-3 bg-light">
Drag me
</div>
<!-- Interactive Examples -->
<button class="btn btn-secondary cursor-not-allowed" disabled>
Disabled Button
</button>
<div class="card cursor-pointer">
<div class="card-body">
<h5 class="card-title">Clickable Card</h5>
<p class="card-text">Entire card is clickable</p>
</div>
</div>
Practical Interaction Examples
Combine multiple utilities for complex interactions:
<!-- Interactive Card Gallery -->
<div class="row g-4">
<div class="col-md-4">
<div class="card position-relative overflow-hidden">
<div class="ratio ratio-4x3">
<img src="image1.jpg" class="object-fit-cover" alt="Image 1">
</div>
<div class="card-body">
<h5 class="card-title">Title</h5>
<p class="card-text text-truncate">Description</p>
<a href="#" class="stretched-link">View More</a>
</div>
</div>
</div>
</div>
<!-- Accessible Loading State -->
<div class="position-relative">
<button class="btn btn-primary" id="submitBtn">
Submit
</button>
<div class="position-absolute top-0 start-0 w-100 h-100 d-none" id="loadingOverlay">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
<!-- Disabled Section with Tooltip -->
<div class="position-relative pe-none opacity-50" data-bs-toggle="tooltip" title="Feature coming soon">
<h4>Premium Feature</h4>
<p>This feature is not yet available.</p>
<button class="btn btn-primary">Upgrade Now</button>
</div>
<!-- Copy-to-Clipboard Code Block -->
<div class="position-relative">
<pre class="user-select-all bg-dark text-white p-3">npm install bootstrap</pre>
<button class="btn btn-sm btn-light position-absolute top-0 end-0 m-2" onclick="copyCode()">
<i class="bi bi-clipboard"></i>
<span class="visually-hidden">Copy code</span>
</button>
</div>
Practice Exercise
Create an interactive media gallery with the following features:
- Grid of 6 images with 1:1 aspect ratio using ratio helpers
- Images use object-fit-cover to fill containers
- Entire card is clickable using stretched links
- Loading overlay with spinner and pe-none during load
- Hover effects that show image title (hidden by default)
- Screen reader text for accessibility
- Responsive: 2 columns on mobile, 3 on tablet, 6 on desktop
Common Mistakes:
- Using
invisible when d-none is needed (or vice versa)
- Forgetting
visually-hidden on icon-only buttons
- Not setting position-relative on stretched link containers
- Using ratio helpers without proper container structure
- Applying object-fit without defined dimensions