Visually hidden text is invisible on screen but still read by screen readers, so icon-only controls, badges and charts get a meaningful description. visually-hidden-focusable shows the element again when it receives keyboard focus. Each example lists what assistive technology announces.
1Icon-only buttons
Hidden text gives icon buttons a name for assistive technology.
Screen reader hears: "Edit profile, button", "Print invoice, button", "Delete item, button"
<div class="d-flex gap-2">
<button type="button" class="btn btn-light"><i class="bi bi-pencil" aria-hidden="true"></i><span class="visually-hidden">Edit profile</span></button>
<button type="button" class="btn btn-light"><i class="bi bi-printer" aria-hidden="true"></i><span class="visually-hidden">Print invoice</span></button>
<button type="button" class="btn btn-light text-danger"><i class="bi bi-trash" aria-hidden="true"></i><span class="visually-hidden">Delete item</span></button>
</div>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Edit profile, button", "Print invoice, button", "Delete item, button"</p>2Skip to content link
visually-hidden-focusable hides the link until a keyboard user tabs to it.
Press Tab inside this box to reveal the skip link.
Screen reader hears: "Skip to main content, link" as the first stop on the page
<div class="border rounded p-3 position-relative">
<a class="visually-hidden-focusable btn btn-primary btn-sm" href="#">Skip to main content</a>
<p class="small text-body-secondary mb-0">Press Tab inside this box to reveal the skip link.</p>
</div>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Skip to main content, link" as the first stop on the page</p>3Hidden search label
The search field keeps a real label for screen readers while the design shows only an icon.
Screen reader hears: "Search the help centre, search field"
<form class="input-group" role="search" style="max-width:22rem">
<label class="visually-hidden" for="hp-q-1244">Search the help centre</label>
<span class="input-group-text"><i class="bi bi-search" aria-hidden="true"></i></span><input class="form-control" id="hp-q-1244" type="search" placeholder="Search">
</form>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Search the help centre, search field"</p>4Action column header
An empty-looking column header still names the column for screen readers.
| Name | Role | Actions |
|---|---|---|
| Ethan Sato | Operations Manager | |
| Felix Tanaka | HR Partner |
Screen reader hears: Column header "Actions"; each button: "More actions for <name>"
<table class="table align-middle mb-0">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Role</th>
<th scope="col"><span class="visually-hidden">Actions</span></th>
</tr>
</thead>
<tbody>
<tr>
<td>Ethan Sato</td>
<td>Operations Manager</td>
<td class="text-end"><button type="button" class="btn btn-sm btn-light"><i class="bi bi-three-dots" aria-hidden="true"></i><span class="visually-hidden">More actions for Ethan Sato</span></button></td>
</tr>
<tr>
<td>Felix Tanaka</td>
<td>HR Partner</td>
<td class="text-end"><button type="button" class="btn btn-sm btn-light"><i class="bi bi-three-dots" aria-hidden="true"></i><span class="visually-hidden">More actions for Felix Tanaka</span></button></td>
</tr>
</tbody>
</table>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> Column header "Actions"; each button: "More actions for <name>"</p>5Badge counts
A counter badge says what it counts only to assistive technology.
Screen reader hears: "Inbox 19 unread messages, button"
<button type="button" class="btn btn-primary position-relative"><i class="bi bi-envelope" aria-hidden="true"></i> Inbox <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill text-bg-danger">19<span class="visually-hidden"> unread messages</span></span></button>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Inbox 19 unread messages, button"</p>6Current page in pagination
Mark the active page in words, not only by colour.
Screen reader hears: "2 (current page)"
<nav aria-label="Search results pages">
<ul class="pagination mb-0">
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item active" aria-current="page"><span class="page-link">2 <span class="visually-hidden">(current page)</span></span></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
</ul>
</nav>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "2 (current page)"</p>7Star rating
The stars are decorative; the hidden text states the score.
Screen reader hears: "Rated 4 out of 5"
<div class="text-warning fs-5">
<i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star" aria-hidden="true"></i><span class="visually-hidden">Rated 4 out of 5</span>
</div>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Rated 4 out of 5"</p>8Loading spinner
Spinners need a text alternative so people know something is happening.
Screen reader hears: "Loading your orders, status"
<div class="d-flex align-items-center gap-2">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading your orders</span>
</div>
<span class="small text-body-secondary" aria-hidden="true">Please wait</span>
</div>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Loading your orders, status"</p>9Required field marker
The red asterisk is hidden from screen readers and replaced by the word required.
Screen reader hears: "Full name (required), edit text, required"
<div style="max-width:20rem">
<label class="form-label" for="hp-name-1245">Full name <span class="text-danger" aria-hidden="true">*</span><span class="visually-hidden">(required)</span></label>
<input class="form-control" id="hp-name-1245" type="text" required>
</div>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Full name (required), edit text, required"</p>10Old and new price
Struck-through prices need words to explain which price is which.
Original price: $106.59Sale price: $81.99
Screen reader hears: "Original price: $106.59 Sale price: $81.99"
<p class="fs-5 mb-0"><del class="text-body-secondary me-2"><span class="visually-hidden">Original price: </span>$106.59</del><strong><span class="visually-hidden">Sale price: </span>$81.99</strong></p>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Original price: $106.59 Sale price: $81.99"</p>11Descriptive read more links
Hidden context turns many identical "Read more" links into distinct ones.
Screen reader hears: "Read more about Headlines people actually click, link"
<div class="vstack gap-2">
<div>
<strong>Headlines people actually click</strong> <a href="#">Read more<span class="visually-hidden"> about Headlines people actually click</span></a>
</div>
<div>
<strong>How to stay motivated when learning online</strong> <a href="#">Read more<span class="visually-hidden"> about How to stay motivated when learning online</span></a>
</div>
</div>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Read more about Headlines people actually click, link"</p>12Progress with a hidden label
A progress bar that also announces its purpose and value.
Screen reader hears: "Profile completion, progress bar, 73%"
<div class="progress" role="progressbar" aria-labelledby="hp-name-1245-p" aria-valuenow="73" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width:73%">
</div>
</div>
<span class="visually-hidden" id="hp-name-1245-p">Profile completion</span>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Profile completion, progress bar, 73%"</p>13Custom close button
A close button drawn with an icon gets its name from hidden text.
Screen reader hears: "Dismiss message, button"
<div class="alert alert-info d-flex align-items-center mb-0">
<span class="me-auto">The mobile app downloads lessons, so you can learn on the train.</span>
<button type="button" class="btn btn-sm btn-link text-info-emphasis" data-ui-dismiss="alert"><i class="bi bi-x-lg" aria-hidden="true"></i><span class="visually-hidden">Dismiss message</span></button>
</div>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Dismiss message, button"</p>14Status dots
Colour alone is not enough: the dot gets a hidden status word.
- Online:Sofia Patel
- Away:Noah Kim
- Offline:Omar Moreau
Screen reader hears: "Online: name", "Away: name", "Offline: name"
<ul class="list-unstyled vstack gap-2 mb-0">
<li class="d-flex align-items-center gap-2"><span class="d-inline-block rounded-circle bg-success" style="width:.6rem;height:.6rem"></span><span class="visually-hidden">Online:</span>Sofia Patel</li>
<li class="d-flex align-items-center gap-2"><span class="d-inline-block rounded-circle bg-warning" style="width:.6rem;height:.6rem"></span><span class="visually-hidden">Away:</span>Noah Kim</li>
<li class="d-flex align-items-center gap-2"><span class="d-inline-block rounded-circle bg-secondary" style="width:.6rem;height:.6rem"></span><span class="visually-hidden">Offline:</span>Omar Moreau</li>
</ul>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Online: name", "Away: name", "Offline: name"</p>15Chart summary
A visual chart is hidden from screen readers and summarised in text instead.
Screen reader hears: "Sales grew from 43 to 79 units over three months."
<figure class="mb-0">
<div class="d-flex align-items-end gap-2 border-bottom" style="height:7rem" aria-hidden="true">
<div class="bg-primary rounded-top flex-fill" style="height:43%">
</div>
<div class="bg-primary rounded-top flex-fill" style="height:73%">
</div>
<div class="bg-primary rounded-top flex-fill" style="height:79%">
</div>
</div>
<figcaption class="visually-hidden">Sales grew from 43 to 79 units over three months.</figcaption>
</figure>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Sales grew from 43 to 79 units over three months."</p>16Social icon links
Each icon link carries the network or channel name as hidden text.
Screen reader hears: "Join our community chat, link", "Subscribe to the blog feed, link", "Email the team, link"
<div class="d-flex gap-3 fs-4">
<a href="#" class="link-secondary"><i class="bi bi-chat-dots" aria-hidden="true"></i><span class="visually-hidden">Join our community chat</span></a><a href="#" class="link-secondary"><i class="bi bi-rss" aria-hidden="true"></i><span class="visually-hidden">Subscribe to the blog feed</span></a><a href="#" class="link-secondary"><i class="bi bi-envelope" aria-hidden="true"></i><span class="visually-hidden">Email the team</span></a>
</div>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Join our community chat, link", "Subscribe to the blog feed, link", "Email the team, link"</p>17Hidden section heading
A heading that only screen readers see keeps the page outline complete.
Featured products
Screen reader hears: "Featured products, heading level 2" and the section is listed as a landmark
<section aria-labelledby="hp-name-1245-h">
<h2 class="visually-hidden" id="hp-name-1245-h">Featured products</h2>
<div class="row g-2">
<div class="col-4">
<img src="../assets/img/product-7.svg" alt="Digital marketing" class="img-fluid rounded">
<div class="small text-truncate">
Digital marketing
</div>
</div>
<div class="col-4">
<img src="../assets/img/product-11.svg" alt="Copywriting" class="img-fluid rounded">
<div class="small text-truncate">
Copywriting
</div>
</div>
<div class="col-4">
<img src="../assets/img/product-10.svg" alt="Leadership skills" class="img-fluid rounded">
<div class="small text-truncate">
Leadership skills
</div>
</div>
</div>
</section>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Featured products, heading level 2" and the section is listed as a landmark</p>18Sortable column state
Arrows show sorting visually; hidden text states it in words.
| Date (sorted ascending) | Amount (sortable) |
|---|---|
| Sep 20, 2025 | $221.14 |
Screen reader hears: "Date (sorted ascending), link", "Amount (sortable), link"
<table class="table mb-0">
<thead>
<tr>
<th scope="col" aria-sort="ascending"><a href="#" class="link-body-emphasis text-decoration-none">Date <i class="bi bi-sort-up" aria-hidden="true"></i><span class="visually-hidden"> (sorted ascending)</span></a></th>
<th scope="col"><a href="#" class="link-body-emphasis text-decoration-none">Amount<span class="visually-hidden"> (sortable)</span></a></th>
</tr>
</thead>
<tbody>
<tr>
<td>Sep 20, 2025</td>
<td>$221.14</td>
</tr>
</tbody>
</table>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Date (sorted ascending), link", "Amount (sortable), link"</p>19Colour swatches
Swatch radio buttons show only colour; the label text is visually hidden.
Screen reader hears: "Colour, group. Midnight black, radio button, checked, 1 of 4"
<fieldset>
<legend class="small fw-semibold">Colour</legend>
<div class="d-flex gap-2">
<input type="radio" class="btn-check" name="hp-colour-1246" id="hp-colour-1246-0" checked>
<label class="btn border rounded-circle p-0 bg-dark" style="width:2rem;height:2rem" for="hp-colour-1246-0"><span class="visually-hidden">Midnight black</span></label>
<input type="radio" class="btn-check" name="hp-colour-1246" id="hp-colour-1246-1">
<label class="btn border rounded-circle p-0 bg-primary" style="width:2rem;height:2rem" for="hp-colour-1246-1"><span class="visually-hidden">Brand blue</span></label>
<input type="radio" class="btn-check" name="hp-colour-1246" id="hp-colour-1246-2">
<label class="btn border rounded-circle p-0 bg-danger" style="width:2rem;height:2rem" for="hp-colour-1246-2"><span class="visually-hidden">Ruby red</span></label>
<input type="radio" class="btn-check" name="hp-colour-1246" id="hp-colour-1246-3">
<label class="btn border rounded-circle p-0 bg-light" style="width:2rem;height:2rem" for="hp-colour-1246-3"><span class="visually-hidden">Snow white</span></label>
</div>
</fieldset>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Colour, group. Midnight black, radio button, checked, 1 of 4"</p>20Step indicator
Each step announces whether it is completed, current or still to come.
- Details (completed)
- 2Shipping (current step)
- 3Payment (not started)
Screen reader hears: "Details (completed)", "Shipping (current step)", "Payment (not started)"
<ol class="list-unstyled d-flex gap-3 mb-0">
<li class="d-flex align-items-center gap-2"><span class="badge rounded-pill text-bg-success"><i class="bi bi-check-lg" aria-hidden="true"></i></span><span>Details<span class="visually-hidden"> (completed)</span></span></li>
<li aria-current="step" class="d-flex align-items-center gap-2"><span class="badge rounded-pill text-bg-primary">2</span><span>Shipping<span class="visually-hidden"> (current step)</span></span></li>
<li class="d-flex align-items-center gap-2"><span class="badge rounded-pill text-bg-secondary">3</span><span>Payment<span class="visually-hidden"> (not started)</span></span></li>
</ol>
<p class="hp-sr small mt-3 mb-0"><i class="bi bi-ear" aria-hidden="true"></i> <strong>Screen reader hears:</strong> "Details (completed)", "Shipping (current step)", "Payment (not started)"</p>