Коли ми кажемо «доступність» (Accessibility, a11y), перша асоціація — сліпі користувачі зі скринридерами. Але реальність набагато ширша: 15% населення Землі мають ту чи іншу форму обмеження — моторну, зорову, слухову або когнітивну. Серед інших — користувачі, що тимчасово не можуть натиснути кнопку (зламана рука), люди похилого віку, або просто ті, хто дивиться контент на сонці без можливості збільшити яскравість.
CSS — один із головних інструментів доступності. Правильно написані стилі роблять інтерфейс читабельним при будь-яких умовах, зручним при навігації клавіатурою, безпечним для людей з епілепсією, та шанобливим до системних налаштувань користувача.
outline: none — злочинПерегляньте будь-який CSS-проєкт 2015–2020 років і ви майже напевно знайдете цей рядок:
/* ⛔ Так НЕ робити — ламає навігацію клавіатурою */
* {
outline: none;
}
Або ще гірше:
/* ⛔ Так теж НЕ робити */
:focus {
outline: none;
}
Цей код прибирає єдиний візуальний індикатор, що показує людям, які навігують клавіатурою (Tab, Shift+Tab, Enter), де зараз знаходиться фокус. Без нього навігація клавіатурою стає неможливою.
Хто використовує навігацію клавіатурою:
:focus-visible — правильне рішення:focus-visible — псевдоклас, що активується лише тоді, коли браузер вважає, що візуальний індикатор фокусу необхідний. Зазвичай це:
При кліку мишею :focus-visible зазвичай не активується — і це правильно. Outline при кліку мишею — лише візуальний шум.
<div class="focus-demo">
<p class="fd-note">Натискайте Tab для навігації між елементами. При кліку мишею — outline не з'являється.</p>
<div class="focus-grid">
<button class="bad-btn">⛔ outline: none<br><small>(клавіатура зламана)</small></button>
<button class="old-btn">⚠️ :focus завжди<br><small>(outline при кліку теж)</small></button>
<button class="good-btn">✅ :focus-visible<br><small>(тільки клавіатура)</small></button>
<a href="#" class="link-good">Посилання ✅</a>
<input class="input-good" type="text" placeholder="Input ✅">
</div>
<div class="focus-style-showcase">
<p class="fss-label">Варіанти кастомного focus-visible стилю:</p>
<button class="fs-ring">Ring style</button>
<button class="fs-underline">Underline style</button>
<button class="fs-glow">Glow style</button>
<button class="fs-contrast">High contrast</button>
</div>
</div>
.focus-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.875rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.fd-note { margin: 0; color: #64748b; font-style: italic; font-size: 0.8rem; }
.focus-grid { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
/* Базові стилі кнопок */
.bad-btn, .old-btn, .good-btn {
padding: 0.6rem 0.9rem;
border-radius: 8px;
border: 1.5px solid;
font-size: 0.8rem;
font-family: inherit;
cursor: pointer;
line-height: 1.3;
text-align: center;
}
/* ⛔ Найгірший варіант */
.bad-btn {
background: #fee2e2; color: #991b1b; border-color: #fca5a5;
outline: none; /* НІКОЛИ ТАК НЕ РОБІТЬ */
}
/* ⚠️ Старий підхід — outline завжди */
.old-btn {
background: #fef3c7; color: #92400e; border-color: #fcd34d;
}
.old-btn:focus {
outline: 3px solid #f59e0b;
outline-offset: 2px;
}
/* ✅ Правильно — :focus-visible */
.good-btn {
background: #dcfce7; color: #166534; border-color: #86efac;
}
.good-btn:focus { outline: none; } /* скидаємо дефолт */
.good-btn:focus-visible {
outline: 3px solid #10b981;
outline-offset: 2px;
border-radius: 8px;
}
.link-good {
color: #6366f1; font-weight: 600;
padding: 0.5rem;
border-radius: 4px;
text-decoration: underline;
}
.link-good:focus { outline: none; }
.link-good:focus-visible {
outline: 2px solid #6366f1;
outline-offset: 3px;
}
.input-good {
padding: 0.5rem 0.75rem;
border: 1.5px solid #d1d5db;
border-radius: 7px;
font-size: 0.875rem;
font-family: inherit;
outline: none;
}
.input-good:focus-visible {
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
}
/* Варіанти кастомного focus-visible */
.focus-style-showcase { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.fss-label { flex-basis: 100%; margin: 0; font-size: 0.78rem; color: #64748b; font-weight: 600; }
.fs-ring, .fs-underline, .fs-glow, .fs-contrast {
padding: 0.5rem 1rem;
border-radius: 7px;
border: 1.5px solid #e2e8f0;
background: white;
color: #1e293b;
font-size: 0.85rem;
font-family: inherit;
cursor: pointer;
}
.fs-ring:focus { outline: none; }
.fs-underline:focus { outline: none; }
.fs-glow:focus { outline: none; }
.fs-contrast:focus { outline: none; }
.fs-ring:focus-visible {
outline: 3px solid #6366f1;
outline-offset: 3px;
}
.fs-underline:focus-visible {
outline: none;
box-shadow: 0 3px 0 0 #6366f1;
}
.fs-glow:focus-visible {
outline: none;
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.35), 0 0 12px rgba(99, 102, 241, 0.2);
}
.fs-contrast:focus-visible {
outline: 3px solid #000;
outline-offset: 2px;
background: #ffff00;
color: #000;
}
/* ✅ Правильний скид: прибираємо дефолт, зберігаємо власний */
:focus {
outline: none;
}
:focus-visible {
outline: 2px solid currentColor;
outline-offset: 3px;
}
/* Або для power-users: скинути дефолт та додати власний на рівні компонентів */
:focus:not(:focus-visible) {
outline: none;
}
WCAG (Web Content Accessibility Guidelines) — міжнародний стандарт доступності. Він визначає мінімальний контраст між кольором тексту та фону:
| Рівень | Текст < 18pt | Текст ≥ 18pt (великий) |
|---|---|---|
| AA (мінімум) | 4.5:1 | 3:1 |
| AAA (ідеал) | 7:1 | 4.5:1 |
<div class="contrast-demo">
<p class="cd-title">Порівняння контрастності:</p>
<div class="contrast-pairs">
<div class="cp-item fail">
<span class="cp-ratio">1.6:1 ❌</span>
<p>Майже невидимо — сірий на білому</p>
</div>
<div class="cp-item warn">
<span class="cp-ratio">3.2:1 ⚠️</span>
<p>Тільки для великого тексту (AA Large)</p>
</div>
<div class="cp-item pass-aa">
<span class="cp-ratio">5.9:1 ✅ AA</span>
<p>Проходить WCAG AA для будь-якого тексту</p>
</div>
<div class="cp-item pass-aaa">
<span class="cp-ratio">7.5:1 ✅ AAA</span>
<p>Відмінна контрастність — ідеал для читання</p>
</div>
</div>
<div class="bg-contrast-demo">
<div class="bc-item bc-dark">
<p>Темний фон + білий текст<br><small>Контраст ~13:1 ✅</small></p>
</div>
<div class="bc-item bc-indigo">
<p>Indigo #6366f1 + білий<br><small>Контраст ~4.6:1 ✅ AA</small></p>
</div>
<div class="bc-item bc-yellow">
<p style="color: #1e293b;">Жовтий + темний текст<br><small>Контраст ~9.5:1 ✅</small></p>
</div>
<div class="bc-item bc-bad">
<p>Поганий вибір 😰<br><small>Контраст ~2.1:1 ❌</small></p>
</div>
</div>
</div>
.contrast-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.875rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.cd-title { margin: 0; font-weight: 700; color: #64748b; }
.contrast-pairs { display: flex; flex-direction: column; gap: 4px; }
.cp-item {
padding: 0.5rem 0.75rem;
border-radius: 6px;
display: flex;
align-items: center;
gap: 1rem;
}
.cp-item p { margin: 0; font-size: 0.82rem; }
.cp-ratio { font-size: 0.78rem; font-weight: 700; white-space: nowrap; flex-shrink: 0; min-width: 90px; }
.fail { background: white; color: #c8c8c8; border: 1px solid #e2e8f0; }
.warn { background: white; color: #94a3b8; border: 1px solid #e2e8f0; }
.pass-aa { background: white; color: #374151; border: 1px solid #e2e8f0; }
.pass-aaa { background: white; color: #111827; border: 1px solid #e2e8f0; }
.bg-contrast-demo { display: flex; gap: 4px; flex-wrap: wrap; }
.bc-item {
flex: 1; min-width: 120px;
padding: 0.75rem;
border-radius: 8px;
}
.bc-item p { margin: 0; font-size: 0.8rem; color: white; line-height: 1.4; }
.bc-item small { font-size: 0.7rem; opacity: 0.85; }
.bc-dark { background: #0f172a; }
.bc-indigo { background: #6366f1; }
.bc-yellow { background: #fbbf24; }
.bc-bad { background: #a78bfa; }
.bc-bad p { color: #c4b5fd; }
forced-colors та @media (prefers-contrast)/* Стилі для режиму підвищеного контрасту (Windows High Contrast) */
@media (forced-colors: active) {
.button {
/* forced-colors змінює кольори автоматично, але border залишається видимим */
border: 2px solid ButtonText;
forced-color-adjust: none; /* вимкнути авто-зміну для окремих елементів */
}
/* Приховані елементи через opacity — можуть бути видимі у forced-colors */
.decorative {
forced-color-adjust: none;
opacity: 0;
}
}
/* Підвищений контраст на прохання користувача */
@media (prefers-contrast: more) {
:root {
--text: #000000;
--bg: #ffffff;
--border: 2px solid currentColor;
}
}
prefers-reduced-motion — безпечні анімаціїДеякі людини страждають на вестибулярні розлади або епілепсію — для них мерехтливі анімації можуть бути фізично небезпечними. Операційні системи надають налаштування «Зменшити рух» (Reduce Motion), і CSS дозволяє його зчитувати.
<div class="motion-demo">
<p class="md-note">Якщо у вашій ОС увімкнено «Зменшити рух» — анімації будуть вимкнені або сповільнені.</p>
<div class="motion-grid">
<div class="motion-card bad-motion">
<div class="spinning-box">⛔<br><small>без prefers-reduced-motion</small></div>
</div>
<div class="motion-card good-motion">
<div class="safe-box">✅<br><small>з prefers-reduced-motion</small></div>
</div>
<div class="motion-card">
<div class="fade-reveal">
<p>Fade-in при скролі<br><small>безпечний, якщо повільний</small></p>
</div>
</div>
</div>
<div class="media-badges">
<div class="mb-item">
<code>@media (prefers-reduced-motion: no-preference)</code>
— повна анімація ✅
</div>
<div class="mb-item">
<code>@media (prefers-reduced-motion: reduce)</code>
— мінімум руху 🛡️
</div>
</div>
</div>
.motion-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.875rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.md-note { margin: 0; font-size: 0.78rem; color: #64748b; font-style: italic; }
.motion-grid { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.motion-card {
flex: 1; min-width: 120px;
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 1rem;
display: flex;
align-items: center;
justify-content: center;
min-height: 80px;
}
/* Анімація БЕЗ урахування налаштувань — небезпечно */
.spinning-box {
width: 60px; height: 60px;
background: linear-gradient(135deg, #ef4444, #f97316);
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-size: 1.2rem;
text-align: center;
line-height: 1.2;
animation: spin-danger 1.5s linear infinite;
}
@keyframes spin-danger { to { transform: rotate(360deg); } }
/* ПРАВИЛЬНО: анімація зупиняється при Reduce Motion */
.safe-box {
width: 60px; height: 60px;
background: linear-gradient(135deg, #10b981, #059669);
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-size: 1.2rem;
text-align: center;
line-height: 1.2;
animation: pulse-safe 2s ease-in-out infinite;
}
@keyframes pulse-safe {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
/* Вимикаємо для тих, хто просить менше руху */
@media (prefers-reduced-motion: reduce) {
.safe-box { animation: none; }
/* Анімації дозволені якщо вони повільні та не обертові */
.fade-reveal { animation-duration: 0.01ms !important; }
/* Загальне правило — вимкнути всі transitions та animations */
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
.fade-reveal {
padding: 0.75rem;
background: #ede9fe;
border-radius: 6px;
text-align: center;
line-height: 1.4;
animation: fade-in-safe 0.6s ease;
}
@keyframes fade-in-safe {
from { opacity: 0; }
to { opacity: 1; }
}
.media-badges { display: flex; flex-direction: column; gap: 0.3rem; }
.mb-item {
background: white;
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 0.5rem 0.75rem;
font-size: 0.78rem;
line-height: 1.4;
}
.mb-item code { background: #f1f5f9; padding: 0.1em 0.3em; border-radius: 3px; font-size: 0.82em; color: #6366f1; }
Правильний паттерн для анімацій:
/* Підхід 1: анімація за замовчуванням, вимикаємо для reduce */
.card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
@media (prefers-reduced-motion: reduce) {
.card {
transition: none;
}
}
/* Підхід 2: анімація лише при no-preference (рекомендований) */
@media (prefers-reduced-motion: no-preference) {
.hero {
animation: slide-in 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
}
}
.sr-only — видимо для скринридера, невидимо візуальноІноді потрібно надати контекст скринридеру, не показуючи текст візуально. Класичний приклад — кнопка «X» (закрити) без текстового пояснення:
<button class="close-btn">
<svg>...</svg>
<span class="sr-only">Закрити діалог</span>
</button>
/* .sr-only — «visually hidden» технніка */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* Варіант з можливістю показу при фокусі (for skip links) */
.sr-only-focusable:not(:focus) {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
<div class="sronly-demo">
<p class="sd-note">Кнопки нижче виглядають однаково, але скринридер читає різний текст:</p>
<div class="btn-row">
<button class="icon-btn bad-icon-btn" title="Видалити">
🗑️
<!-- Скринридер: "button" або нічого корисного -->
</button>
<button class="icon-btn good-icon-btn" title="Видалити">
🗑️
<span class="sr-only-ex">Видалити елемент</span>
<!-- Скринридер: "Видалити елемент" ✅ -->
</button>
</div>
<div class="skip-link-demo">
<a href="#main-content" class="skip-link">Перейти до основного контенту</a>
<p class="sd-note" style="margin-top:0">↑ Skip Link — з'являється при Tab (спробуйте)</p>
</div>
<div class="visually-hidden-compare">
<p><code>display: none</code> — прихований від всіх, включно зі скринридером</p>
<p><code>visibility: hidden</code> — прихований візуально + від скринридера, зберігає місце</p>
<p><code>opacity: 0</code> — невидимий, але скринридер ЧИТАЄ його</p>
<p><code>.sr-only</code> — невидимий, але скринридер ЧИТАЄ ✅</p>
</div>
</div>
.sronly-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.875rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.sd-note { margin: 0; color: #64748b; font-style: italic; font-size: 0.8rem; }
.btn-row { display: flex; gap: 0.5rem; }
.icon-btn {
width: 40px; height: 40px;
border-radius: 8px;
border: 1.5px solid #e2e8f0;
background: white;
font-size: 1.1rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s;
}
.bad-icon-btn { border-color: #fca5a5; background: #fff5f5; }
.good-icon-btn { border-color: #86efac; background: #f0fdf4; }
.icon-btn:hover { transform: scale(1.05); }
/* .sr-only техніка */
.sr-only-ex {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* Skip Link */
.skip-link-demo { display: flex; flex-direction: column; gap: 0.3rem; }
.skip-link {
display: inline-block;
padding: 0.5rem 1rem;
background: #6366f1;
color: white;
border-radius: 6px;
text-decoration: none;
font-size: 0.85rem;
font-weight: 600;
transform: translateY(-120%);
transition: transform 0.15s;
}
.skip-link:focus {
transform: translateY(0);
outline: 3px solid #000;
outline-offset: 2px;
}
.visually-hidden-compare {
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 0.75rem;
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.visually-hidden-compare p { margin: 0; font-size: 0.78rem; line-height: 1.4; }
.visually-hidden-compare code { background: #f1f5f9; padding: 0.1em 0.3em; border-radius: 3px; color: #6366f1; font-size: 0.85em; }
px для font-size на :root — помилкаБраузери дозволяють користувачам встановлювати базовий розмір шрифту у налаштуваннях (за замовчуванням 16px). Люди з вадами зору встановлюють 20px або 24px. Якщо ви задаєте font-size: 16px на :root — ви перевизначаєте їх налаштування.
<div class="font-scale-demo">
<div class="fsd-bad">
<p class="fsd-label">❌ font-size: 16px на :root</p>
<p class="text-px">Цей текст завжди 16px — ігнорує налаштування браузера</p>
</div>
<div class="fsd-good">
<p class="fsd-label">✅ Без font-size на :root (або 100%)</p>
<p class="text-inherit">Цей текст поважає налаштування браузера — масштабується разом з ним</p>
</div>
<div class="touch-targets">
<p class="fsd-label">Touch Targets — мінімум 44×44px (WCAG 2.5.5)</p>
<div class="tt-grid">
<button class="tt-bad">Замалий<br><small>24×24px ❌</small></button>
<button class="tt-ok">Нормальний<br><small>36×36px ⚠️</small></button>
<button class="tt-good">Правильний<br><small>44×44px ✅</small></button>
<button class="tt-great">Зручний<br><small>48×48px 🎯</small></button>
</div>
</div>
</div>
.font-scale-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.fsd-label { margin: 0 0 0.4rem; font-size: 0.78rem; font-weight: 700; color: #374151; }
.fsd-bad, .fsd-good {
background: white;
border-radius: 8px;
padding: 0.75rem;
border: 1.5px solid;
}
.fsd-bad { border-color: #fca5a5; }
.fsd-good { border-color: #86efac; }
.text-px { margin: 0; font-size: 16px; line-height: 1.5; } /* ❌ fіксований */
.text-inherit { margin: 0; font-size: 1rem; line-height: 1.5; } /* ✅ відносний */
.touch-targets { background: white; border-radius: 8px; padding: 0.75rem; border: 1px solid #e2e8f0; }
.tt-grid { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: flex-end; }
.tt-bad, .tt-ok, .tt-good, .tt-great {
border-radius: 8px;
border: none;
font-family: inherit;
cursor: pointer;
font-size: 0.7rem;
text-align: center;
line-height: 1.3;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.tt-bad { width: 60px; height: 44px; background: #fee2e2; color: #991b1b; }
.tt-ok { width: 60px; height: 48px; background: #fef3c7; color: #92400e; }
.tt-good { width: 60px; height: 56px; background: #dcfce7; color: #166534; }
.tt-great { width: 60px; height: 60px; background: #ede9fe; color: #5b21b6; }
.tt-bad small, .tt-ok small, .tt-good small, .tt-great small { font-size: 0.65rem; margin-top: 0.15rem; }
@media print — стилі для друку<div class="print-demo">
<p class="prd-note">Нижче показано, як виглядатиме сторінка при друку (симуляція):</p>
<div class="simulated-print">
<div class="print-hide">🎨 Навігація та декоративні елементи приховані ✅</div>
<article class="print-article">
<h2 class="print-heading">Заголовок статті</h2>
<p>Основний текст — чорний на білому, без фонів та тіней. Шрифт serif для кращого читання на папері.</p>
<a href="https://example.com" class="print-link">Посилання (URL показується при друці)</a>
</article>
<div class="print-hide">🎯 Бокова панель прихована ✅</div>
</div>
<p class="print-code-note">CSS для print:</p>
<pre class="print-code">@media print {
nav, aside, footer { display: none; }
body { font: 12pt/1.5 Georgia, serif; color: #000; }
a::after { content: " (" attr(href) ")"; font-size: 10pt; }
h1, h2 { page-break-after: avoid; }
img { max-width: 100%; }
}</pre>
</div>
.print-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.875rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.prd-note { margin: 0; color: #64748b; font-style: italic; font-size: 0.78rem; }
.simulated-print {
background: white;
border: 2px dashed #94a3b8;
border-radius: 8px;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
font-size: 0.85rem;
}
.print-hide {
background: #f1f5f9;
border: 1px dashed #cbd5e1;
border-radius: 4px;
padding: 0.4rem 0.6rem;
color: #94a3b8;
font-size: 0.75rem;
text-align: center;
text-decoration: line-through;
}
.print-article { padding: 0.5rem 0; }
.print-heading { margin: 0 0 0.5rem; font-size: 1.1rem; color: #000; }
.print-article p { margin: 0 0 0.5rem; color: #111; line-height: 1.5; }
.print-link { color: #000; }
.print-link::after { content: " (https://example.com)"; font-size: 0.75rem; color: #666; }
.print-code-note { margin: 0; font-size: 0.78rem; font-weight: 700; color: #64748b; }
.print-code {
background: #1e293b;
color: #e2e8f0;
border-radius: 8px;
padding: 0.75rem 1rem;
font-size: 0.78rem;
overflow-x: auto;
margin: 0;
line-height: 1.5;
}
🎯 Фокус
outline: none без заміни:focus-visible замість :focusTab → перехід до контенту)🎨 Кольори
forced-colors: active режимі⚡ Анімації
@media (prefers-reduced-motion: no-preference) для анімаційscroll-behavior: smooth — лише при no-preference📏 Розміри
font-size на :root — тільки % або нічого44px × 44px👁️ Приховування
.sr-only для тексту що потрібен скринридерамdisplay: none = невидимий для всіх включно зі скринридеромopacity: 0 ≠ приховано від скринридера!🖨️ Друк
@media print — ховаємо nav, aside, adsbody { font: 12pt serif; color: #000; }a::after { content: " (" attr(href) ")"; }Беремо готовий HTML-компонент навігаційного меню. Знайдіть та виправте 4 проблеми доступності:
/* Знайдіть проблеми: */
* { outline: none; } /* Проблема 1 */
nav a { color: #aaaaaa; } /* Проблема 2: контраст ~2.3:1 */
.menu-icon { width: 20px; height: 20px; } /* Проблема 3: замалий touch target */
.tooltip { opacity: 0; } /* Проблема 4: чи видно скринридеру? */
Реалізуйте систему анімацій з повним урахуванням prefers-reduced-motion:
@keyframesreduce: всі переходи ≤ 50ms, без scale і translateПобудуйте форму відправки повідомлень з:
:focus-visible на кожному полі:user-valid/:user-invalid з ARIA-feedback через aria-live region.sr-only для скринридерів@media print стиль що показує текст полів без дизайну формCSS для форм та інтерактивних станів
Повне керівництво зі стилізації HTML-форм: псевдокласи :focus-visible, :user-valid, :has(), accent-color, кастомні checkbox/radio/select, floating label патерн, валідація без JavaScript та нативний CSS-only дизайн форм.
CSS-функції та сучасні sizing primitives
Повний огляд CSS математичних функцій: calc(), min(), max(), clamp(), round(), mod(), тригонометричні sin/cos/tan, env() для безпечних зон, та сучасні layout primitives — The Stack, The Sidebar, The RAM, The Reel.