UI Components Easy
Screen Reader Only Utilities
CSS utility classes and patterns for visually hidden content that remains accessible to screen readers and assistive technology.
Open in Lab
MCP
css
Targets: JS HTML
Code
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Inter", system-ui, -apple-system, sans-serif;
background: #0a0a0a;
color: #e4e4e7;
line-height: 1.6;
min-height: 100vh;
}
/* ==============================
Screen Reader Only Utilities
============================== */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.sr-only-focusable:focus,
.sr-only-focusable:active {
position: static;
width: auto;
height: auto;
padding: 0.6rem 1.2rem;
margin: 0;
overflow: visible;
clip: auto;
clip-path: none;
white-space: normal;
}
/* Reveal mode */
body.reveal-mode .sr-only:not(.sr-only-focusable) {
position: static;
width: auto;
height: auto;
padding: 0.15rem 0.4rem;
margin: 0;
overflow: visible;
clip: auto;
clip-path: none;
white-space: normal;
background: rgba(139, 92, 246, 0.15);
color: #c084fc;
border: 1px dashed rgba(139, 92, 246, 0.4);
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
}
body.reveal-mode .sr-reveal {
display: flex !important;
}
/* ============================== */
.demo {
max-width: 780px;
margin: 0 auto;
padding: 3rem 1.5rem;
}
.demo-title {
font-size: 1.75rem;
font-weight: 700;
color: #fafafa;
letter-spacing: -0.02em;
}
.demo-sub {
color: #a1a1aa;
margin-top: 0.25rem;
font-size: 0.95rem;
}
/* Toggle Bar */
.toggle-bar {
margin-top: 1.5rem;
display: flex;
justify-content: center;
}
.btn {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.55rem 1rem;
font-size: 0.825rem;
font-weight: 500;
font-family: inherit;
border: 1px solid #27272a;
border-radius: 8px;
background: #18181b;
color: #e4e4e7;
cursor: pointer;
transition: all 0.15s;
}
.btn:hover {
background: #27272a;
}
.btn:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
.btn--reveal[aria-pressed="true"] {
background: rgba(139, 92, 246, 0.15);
border-color: rgba(139, 92, 246, 0.3);
color: #c084fc;
}
/* Example Sections */
.example-section {
margin-top: 2rem;
background: #111113;
border: 1px solid #27272a;
border-radius: 12px;
padding: 1.5rem;
}
.example-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.5rem;
}
.example-title {
font-size: 1.05rem;
font-weight: 600;
color: #fafafa;
}
.example-badge {
font-size: 0.65rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
padding: 0.15rem 0.5rem;
border-radius: 100px;
}
.example-badge--green {
background: rgba(34, 197, 94, 0.15);
color: #4ade80;
border: 1px solid rgba(34, 197, 94, 0.3);
}
.example-badge--blue {
background: rgba(59, 130, 246, 0.15);
color: #60a5fa;
border: 1px solid rgba(59, 130, 246, 0.3);
}
.example-badge--amber {
background: rgba(245, 158, 11, 0.15);
color: #fbbf24;
border: 1px solid rgba(245, 158, 11, 0.3);
}
.example-badge--purple {
background: rgba(139, 92, 246, 0.15);
color: #a78bfa;
border: 1px solid rgba(139, 92, 246, 0.3);
}
.example-desc {
font-size: 0.85rem;
color: #a1a1aa;
margin-bottom: 1rem;
}
.example-desc code {
background: #1e1e22;
padding: 0.1rem 0.35rem;
border-radius: 4px;
font-size: 0.78rem;
color: #d4d4d8;
font-family: "SF Mono", "Fira Code", monospace;
}
kbd {
display: inline-block;
padding: 0.08rem 0.4rem;
background: #1e1e22;
border: 1px solid #3f3f46;
border-radius: 4px;
font-size: 0.73rem;
font-family: "SF Mono", "Fira Code", monospace;
color: #d4d4d8;
}
.example-demo {
background: #09090b;
border: 1px solid #1e1e22;
border-radius: 8px;
padding: 1.25rem;
}
.example-hint {
margin-top: 0.75rem;
font-size: 0.78rem;
color: #52525b;
text-align: center;
}
/* Skip Link Demo */
.skip-link-demo {
position: relative;
min-height: 100px;
}
.skip-link {
display: block;
padding: 0.6rem 1.2rem;
background: #3b82f6;
color: #fff;
text-decoration: none;
font-weight: 600;
font-size: 0.85rem;
border-radius: 8px;
z-index: 10;
margin-bottom: 0.5rem;
}
.skip-link:focus {
outline: 2px solid #60a5fa;
outline-offset: 2px;
}
.fake-nav {
display: flex;
gap: 0.75rem;
padding: 0.5rem 0.75rem;
background: #18181b;
border-radius: 6px;
margin-bottom: 0.5rem;
}
.fake-nav-item {
font-size: 0.78rem;
color: #71717a;
}
.fake-main {
padding: 1rem;
background: #18181b;
border-radius: 6px;
font-size: 0.85rem;
color: #52525b;
text-align: center;
}
/* Icon Buttons */
.icon-buttons {
display: flex;
gap: 0.75rem;
justify-content: center;
}
.icon-btn {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
background: #18181b;
border: 1px solid #27272a;
border-radius: 10px;
color: #a1a1aa;
cursor: pointer;
transition: all 0.15s;
position: relative;
}
.icon-btn:hover {
background: #27272a;
color: #fafafa;
}
.icon-btn:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* Image Examples */
.image-examples {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.image-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.image-placeholder {
width: 100%;
height: 100px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.image-placeholder--decorative {
background: #18181b;
border: 1px solid #27272a;
color: #3f3f46;
}
.image-placeholder--info {
background: rgba(59, 130, 246, 0.08);
border: 1px solid rgba(59, 130, 246, 0.2);
color: #60a5fa;
}
.image-type {
font-size: 0.8rem;
font-weight: 500;
color: #d4d4d8;
}
.image-code {
font-size: 0.7rem;
color: #71717a;
font-family: "SF Mono", "Fira Code", monospace;
background: #1e1e22;
padding: 0.15rem 0.4rem;
border-radius: 4px;
}
/* SR Reveal */
.sr-reveal {
display: none;
align-items: flex-start;
gap: 0.5rem;
margin-top: 0.75rem;
padding: 0.75rem;
background: rgba(139, 92, 246, 0.08);
border: 1px solid rgba(139, 92, 246, 0.2);
border-radius: 8px;
}
.sr-reveal-label {
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #a78bfa;
white-space: nowrap;
margin-top: 0.1rem;
}
.sr-reveal-text {
font-size: 0.825rem;
color: #c4b5fd;
font-style: italic;
}
/* Techniques Grid */
.techniques-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.technique-card {
background: #09090b;
border: 1px solid #1e1e22;
border-radius: 10px;
padding: 1rem;
}
.technique-header {
display: flex;
align-items: center;
gap: 0.6rem;
margin-bottom: 0.75rem;
}
.technique-status {
font-size: 0.6rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
padding: 0.15rem 0.45rem;
border-radius: 4px;
flex-shrink: 0;
}
.technique-status--good {
background: rgba(34, 197, 94, 0.15);
color: #4ade80;
border: 1px solid rgba(34, 197, 94, 0.3);
}
.technique-status--bad {
background: rgba(239, 68, 68, 0.15);
color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.3);
}
.technique-status--focus {
background: rgba(59, 130, 246, 0.15);
color: #60a5fa;
border: 1px solid rgba(59, 130, 246, 0.3);
}
.technique-status--warn {
background: rgba(245, 158, 11, 0.15);
color: #fbbf24;
border: 1px solid rgba(245, 158, 11, 0.3);
}
.technique-name {
font-size: 0.85rem;
font-weight: 600;
color: #e4e4e7;
}
.technique-code {
background: #111113;
border: 1px solid #1e1e22;
border-radius: 6px;
padding: 0.65rem 0.85rem;
overflow-x: auto;
margin-bottom: 0.5rem;
}
.technique-code code {
font-size: 0.72rem;
font-family: "SF Mono", "Fira Code", monospace;
color: #a1a1aa;
line-height: 1.6;
white-space: pre;
}
.technique-note {
font-size: 0.78rem;
color: #71717a;
line-height: 1.5;
}
.technique-card--good {
border-color: rgba(34, 197, 94, 0.15);
}
.technique-card--bad {
border-color: rgba(239, 68, 68, 0.15);
}
.technique-card--warn {
border-color: rgba(245, 158, 11, 0.15);
}
/* Scrollbar */
.technique-code::-webkit-scrollbar {
height: 3px;
}
.technique-code::-webkit-scrollbar-track {
background: transparent;
}
.technique-code::-webkit-scrollbar-thumb {
background: #27272a;
border-radius: 4px;
}(() => {
const toggleBtn = document.getElementById("toggle-reveal");
toggleBtn.addEventListener("click", () => {
const isRevealed = document.body.classList.toggle("reveal-mode");
toggleBtn.setAttribute("aria-pressed", isRevealed ? "true" : "false");
// Show/hide the sr-reveal panels
document.querySelectorAll(".sr-reveal").forEach((el) => {
el.hidden = !isRevealed;
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Screen Reader Only Utilities</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<h1 class="demo-title">Screen Reader Only Utilities</h1>
<p class="demo-sub">CSS patterns for visually hidden content that remains accessible to assistive technology.</p>
<div class="toggle-bar">
<button class="btn btn--reveal" id="toggle-reveal" aria-pressed="false">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
Reveal Hidden Content
</button>
</div>
<!-- Skip Link -->
<section class="example-section">
<div class="example-header">
<h2 class="example-title">Skip Link</h2>
<span class="example-badge example-badge--green">sr-only-focusable</span>
</div>
<p class="example-desc">Invisible until focused via <kbd>Tab</kbd>. Lets keyboard users skip to main content.</p>
<div class="example-demo">
<div class="skip-link-demo">
<a href="#main-content" class="sr-only sr-only-focusable skip-link">Skip to main content</a>
<div class="fake-nav" aria-hidden="true">
<span class="fake-nav-item">Logo</span>
<span class="fake-nav-item">Home</span>
<span class="fake-nav-item">About</span>
<span class="fake-nav-item">Contact</span>
</div>
<div class="fake-main" id="main-content" aria-hidden="true">
<span>Main Content Area</span>
</div>
</div>
<div class="example-hint">Press <kbd>Tab</kbd> into this area to see the skip link appear</div>
</div>
<div class="sr-reveal" hidden>
<span class="sr-reveal-label">Screen reader sees:</span>
<span class="sr-reveal-text">"Skip to main content" (link)</span>
</div>
</section>
<!-- Icon Buttons -->
<section class="example-section">
<div class="example-header">
<h2 class="example-title">Icon Buttons with SR Labels</h2>
<span class="example-badge example-badge--blue">sr-only</span>
</div>
<p class="example-desc">Icon-only buttons with visually hidden text labels for screen readers.</p>
<div class="example-demo">
<div class="icon-buttons">
<button class="icon-btn" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
<span class="sr-only">Edit item</span>
</button>
<button class="icon-btn" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
<span class="sr-only">Delete item</span>
</button>
<button class="icon-btn" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>
<span class="sr-only">Share item</span>
</button>
<button class="icon-btn" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
<span class="sr-only">Add to favorites</span>
</button>
</div>
</div>
<div class="sr-reveal" hidden>
<span class="sr-reveal-label">Screen reader sees:</span>
<span class="sr-reveal-text">"Edit item" (button), "Delete item" (button), "Share item" (button), "Add to favorites" (button)</span>
</div>
</section>
<!-- Decorative vs Informative Images -->
<section class="example-section">
<div class="example-header">
<h2 class="example-title">Decorative vs Informative Images</h2>
<span class="example-badge example-badge--amber">alt text</span>
</div>
<p class="example-desc">Decorative images use <code>alt=""</code> and <code>aria-hidden="true"</code>. Informative images need descriptive alt text.</p>
<div class="example-demo">
<div class="image-examples">
<div class="image-card">
<div class="image-placeholder image-placeholder--decorative" role="img" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
</div>
<span class="image-type">Decorative</span>
<code class="image-code">alt="" aria-hidden="true"</code>
</div>
<div class="image-card">
<div class="image-placeholder image-placeholder--info" role="img" aria-label="Bar chart showing monthly revenue increasing from $12k in January to $28k in June">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>
</div>
<span class="image-type">Informative</span>
<code class="image-code">alt="Bar chart showing..."</code>
</div>
</div>
</div>
<div class="sr-reveal" hidden>
<span class="sr-reveal-label">Screen reader sees:</span>
<span class="sr-reveal-text">Decorative: (skipped). Informative: "Bar chart showing monthly revenue increasing from $12k in January to $28k in June"</span>
</div>
</section>
<!-- CSS Techniques Comparison -->
<section class="example-section">
<div class="example-header">
<h2 class="example-title">CSS Techniques Comparison</h2>
<span class="example-badge example-badge--purple">reference</span>
</div>
<p class="example-desc">Different CSS methods for hiding content and their effect on screen readers.</p>
<div class="techniques-grid">
<div class="technique-card technique-card--good">
<div class="technique-header">
<span class="technique-status technique-status--good">SR reads</span>
<h3 class="technique-name">.sr-only (clip-rect)</h3>
</div>
<pre class="technique-code"><code>.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}</code></pre>
<p class="technique-note">The gold standard. Content is invisible but accessible.</p>
</div>
<div class="technique-card technique-card--good">
<div class="technique-header">
<span class="technique-status technique-status--good">SR reads</span>
<h3 class="technique-name">.sr-only (clip-path)</h3>
</div>
<pre class="technique-code"><code>.sr-only-alt {
position: absolute;
clip-path: inset(50%);
width: 1px;
height: 1px;
overflow: hidden;
white-space: nowrap;
}</code></pre>
<p class="technique-note">Modern alternative using clip-path. Same result.</p>
</div>
<div class="technique-card technique-card--bad">
<div class="technique-header">
<span class="technique-status technique-status--bad">SR skips</span>
<h3 class="technique-name">display: none</h3>
</div>
<pre class="technique-code"><code>.hidden {
display: none;
}</code></pre>
<p class="technique-note">Hides from everyone, including screen readers. Do not use for accessible hiding.</p>
</div>
<div class="technique-card technique-card--bad">
<div class="technique-header">
<span class="technique-status technique-status--bad">SR skips</span>
<h3 class="technique-name">visibility: hidden</h3>
</div>
<pre class="technique-code"><code>.invisible {
visibility: hidden;
}</code></pre>
<p class="technique-note">Also hides from screen readers. Element still takes up space.</p>
</div>
<div class="technique-card technique-card--good">
<div class="technique-header">
<span class="technique-status technique-status--focus">Visible on focus</span>
<h3 class="technique-name">.sr-only-focusable</h3>
</div>
<pre class="technique-code"><code>.sr-only-focusable:focus,
.sr-only-focusable:active {
position: static;
width: auto;
height: auto;
clip: auto;
clip-path: none;
white-space: normal;
overflow: visible;
margin: 0;
}</code></pre>
<p class="technique-note">Extends .sr-only. Becomes visible when focused (skip links).</p>
</div>
<div class="technique-card technique-card--warn">
<div class="technique-header">
<span class="technique-status technique-status--warn">Depends</span>
<h3 class="technique-name">aria-hidden="true"</h3>
</div>
<pre class="technique-code"><code><span aria-hidden="true">
Decorative content
</span></code></pre>
<p class="technique-note">Hides from screen readers but remains visually visible. Use for decorative elements.</p>
</div>
</div>
</section>
</div>
<script src="script.js"></script>
</body>
</html>A comprehensive reference of CSS techniques for hiding content visually while keeping it accessible to screen readers. Compares clip-rect, clip-path, and absolute positioning methods, and contrasts them with display:none and visibility:hidden which hide from assistive technology too.