Componentes UI Easy
Screen Reader Only Utilities
CSS utility classes and patterns for visually hidden content that remains accessible to screen readers and assistive technology.
Abrir no 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.