Перемикач теми інтерфейсу
1) Принципи
1. Система> скін. Тема - не просто інверсія фону, а набір токенів (колір, фон, контраст, тіні, стани, ілюстрації, графіки).
2. System-first. За замовчуванням - System ('prefers-color-scheme') з можливістю вручну вибрати Light/Dark/High-Contrast.
3. Типовий контраст. Мета - WCAG AA, для дрібного тексту/важливих міток - AAA.
4. Ніяких спалахів. Застосовуємо тему до рендера (inline-скрипт), а переходи робимо акуратно.
5. Стабільність бренду. Акценти і семантика статусів зберігаються у всіх темах.
2) Режими та сценарії
Light - денні сценарії/платіжні форми/довге читання.
Dark - вечір/низька освітленість/лайв-матчі; знижує відблиски.
System - слідуємо ОС/браузеру ('prefers-color-scheme').
High-Contrast - підвищений контраст і мінімізація прикрас (вкл. reduce motion).
Seasonal/Promo (опціонально) - поверх базової теми під турнір/івент (не ламає токени).
3) Архітектура токенів
Зберігаємо семантичні токени, а не прямі кольори:css
:root {
/ semantics/
--bg: hsl(0 0% 99%);
--bg-elev: hsl(0 0% 100%);
--fg: hsl(220 15% 15%);
--muted: hsl(220 10% 45%);
--accent: hsl (260 95% 60%) ;/brand accent/
--success: hsl(152 55% 40%);
--warning: hsl(36 85% 45%);
--danger: hsl(0 75% 50%);
--border: hsl(220 10% 90%);
--focus: hsl(260 95% 60% /.6);
--shadow: 0 6px 24px hsl(220 20% 10% /.08);
/ typography/radii/
--radius: 12px;
--lh: 1. 4;
}
:root[data-theme="dark"]{
--bg: hsl(220 18% 10%);
--bg-elev: hsl(220 18% 14%);
--fg: hsl(0 0% 96%);
--muted: hsl(220 10% 70%);
--accent: hsl (260 95% 65%) ;/slightly lighter in the dark/
--border: hsl(220 14% 22%);
--shadow: 0 8px 28px hsl(220 50% 2% /.6);
}
:root[data-theme="hc"]{
/ high-contrast: simple pairs, high Lc/
--bg: #000; --bg-elev:#000; --fg:#fff; --muted:#fff;
--accent:#00E; --success:#0F0; --warning:#FF0; --danger:#F00;
--border:#fff; --focus:#0FF;
}
Правило: компоненти використовують тільки семантичні токени.
4) Детектор і збереження вибору
html
<script>
(function(){
const saved = localStorage. getItem('theme'); // 'light' 'dark' 'hc' 'system' null const sys = window. matchMedia('(prefers-color-scheme: dark)'). matches? 'dark': 'light';
const theme = saved && saved!=='system'? saved: sys;
document. documentElement. setAttribute('data-theme', theme);
})();
</script>
UI-перемикач: `Light / Dark / System / High-Contrast`. При виборі «System» не зберігайте конкретний колір, тільки прапор. Слухайте зміни ОС:
js matchMedia('(prefers-color-scheme: dark)'). addEventListener('change', e=>{
if(localStorage. getItem('theme')==='system'){
document. documentElement. setAttribute('data-theme', e. matches? 'dark': 'light');
}
});
5) Плавні переходи без FOUC
Застосовуйте тему до завантаження CSS (inline-скрипт).
Анімації теми - короткі і тільки'color/background/border-color'( 120-200 мс), але не при першому рендері:css
@media (prefers-reduced-motion: no-preference){
html. theme-ready { transition: color. 18s, background-color. 18s, border-color. 18s; }
}
Після монтування програми додайте'class =» theme-ready»'.
6) Компоненти та стани
Текст/іконки: контраст ≥ AA; вторинний текст не нижче 4. 5:1 (в дарці легко «вицвітає»).
Поля/картки: фон'--bg-elev', межа'--border', тінь'--shadow'.
CTA: фон «--accent», текст контрастний («#fff» або обчислюваний).
Стани (hover/focus/active/disabled): змінюйте яскравість/альфу, а не «переливайте веселку».
Графіки/спарклайни: окремі панелі для light/dark; сітка/осі низькоконтрастні, але читабельні.
7) Зображення/медіа/логотипи
Іконки монохромні - через'currentColor'( підлаштовуються під текст).
Логотипи брендів не інвертуйте; готуйте дві версії (light/dark).
Постери/скріншоти: легкий overlay в дарці (8-12%) для читабельності текстів.
SVG: уникайте «жорстких» заливок, використовуйте варс'var (--fg) '/' var (--accent)'.
8) Доступність
Високий контраст: окремий пресет'data-theme =» hc»'.
Фокус-кільця: завжди видимі ('outline: 2px solid var(--focus); outline-offset: 2px`).
Не покладайтеся на колір. Іконка/текст/патерн для статусів.
Шрифти: `font-variant-numeric: tabular-nums;'для сум/коефів.
RTL: тема не ламає дзеркалювання (використовуємо логічні властивості).
9) Перформанс
Кольори - CSS custom properties на корені → миттєве перевикористання без ререндера компонентів.
Уникайте «перефарбовування» зображень фільтрами'invert ()'на великих контейнерах.
Лінива підміна важких ілюстрацій для теми (якщо потрібні).
Не зберігайте великі палітри в JS - тема управляється класом/атрибутом.
10) Специфіка iGaming
Live-коефіцієнти вночі: «м'який» контраст (AAA для чисел), підсвічування зміни коефіцієнта - ненав'язлива, без мерехтінь.
Відповідальна гра: нагадування та підказки читабельні в обох темах; без «отруйних» квітів вночі.
Каса: поля/підписи/помилки без стомлюючих світних акцентів; успіх/помилка стабільні по темі.
Турнірні «скіни»: тільки як поверхневі accent-override, не ламайте базові токени.
11) Сніпети UI
Перемикач (HTML/JS):html
<label class="theme-switch">
<span> Topic </span>
<select id="theme">
<option value = "system "> System </option>
<option value = "light "> Bright </option>
<option value = "dark "> Dark </option>
<option value = "hc"> High contrast </option>
</select>
</label>
<script>
const sel = document. getElementById('theme');
sel. value = localStorage. getItem('theme') 'system';
sel. addEventListener('change', e=>{
const v = e. target. value;
localStorage. setItem('theme', v);
if(v==='system'){
const sys = matchMedia('(prefers-color-scheme: dark)'). matches? 'dark':'light';
document. documentElement. setAttribute('data-theme', sys);
} else {
document. documentElement. setAttribute('data-theme', v);
}
});
</script>
Компонентні пресети:
css
.btn{height:44px; padding:0 16px; border-radius:var(--radius); display:inline-flex; align-items:center; gap:8px}
.btn--primary{background:var(--accent); color:#fff}
.card{background:var(--bg-elev); border:1px solid var(--border); box-shadow:var(--shadow); border-radius:var(--radius)}
.text-muted{color:var(--muted)}
React hook (persist + system):
tsx import { useEffect, useState } from 'react';
type Theme = 'light' 'dark' 'hc' 'system';
export function useTheme(){
const [theme, setTheme] = useState<Theme>(()=>localStorage. getItem('theme') as Theme 'system');
useEffect(()=>{
const apply = (t:Theme)=>{
const v = t==='system'? (matchMedia('(prefers-color-scheme: dark)'). matches? 'dark':'light'): t;
document. documentElement. setAttribute('data-theme', v);
};
apply(theme);
const mql = matchMedia('(prefers-color-scheme: dark)');
const on = ()=> theme==='system' && apply('system');
mql. addEventListener('change', on);
return ()=> mql. removeEventListener('change', on);
},[theme]);
useEffect(()=> localStorage. setItem('theme', theme),[theme]);
return { theme, setTheme };
}
12) Метрики
Theme adoption rate: частка користувачів на Dark/System/HC.
FOUC rate: частка з видимим «білим сплеском» при старті (<1%).
Contrast defects: кількість багів контрасту по релізу.
Error visibility: кліки/повтори через «непомітні» помилки в різних темах.
Energy impact (мобайл): порівняння часу сесії в дарці vs гавкіт (непряма метрика).
13) Анти-патерни
Інвертувати все'filter: invert (1)'- ламає бренд і смисли.
Змінювати кольори безпосередньо в компонентах без токенів.
Ховати фокус-кільця в темній темі.
Занадто темний текст на темному фоні (або світлий на світлому).
Довгі transition на всю сторінку (пригальмовування).
«Ексклюзивні» кольори статусів в одній темі, яких немає в іншій.
14) QA-чек-лист
Контраст і читаність
- Всі тексти ≥ AA; критичні мітки/дрібний текст ≥ AAA.
- Помилки/успіх/попередження помітні не тільки кольором.
Поведінка
- System поважає'prefers-color-scheme'і реагує на зміну ОС.
- Немає FOUC (тема застосовується до рендера).
- Перемикання теми не скидає стан сторінок.
Компоненти
- Картки/форми/таблиці використовують тільки токени.
- Графіки мають палітри для обох тем.
- Логотипи/іконки коректно видно в обох темах.
A11y
- Видимий focus-ring; High-Contrast режим доступний.
- Враховується'prefers-reduced-motion'.
Перформанс
- Перехід ≤ 200 мс; без глобальних reflow.
- Немає важких фільтрів/блендів на контейнерах.
15) Документація в дизайн-системі
Theme tokens: палітри, контрасти, стан (hover/focus/active/disabled).
Guides: як додавати новий брендо-акцент без регресу контрасту.
Charts/Media: навмисні панелі для light/dark.
Patterns: System-first, High-Contrast, плавне перемикання без FOUC.
Do/Don’t: інверсія фільтром, inline-кольору, невидимі помилки/фокус.
Коротке резюме
Робочий перемикач теми - це семантичні токени + System-first + безмерехтливий старт. Фіксуйте контраст, централізуйте кольори, поважайте'prefers-color-scheme'і reduce-motion, зберігайте вибір користувача і уникайте важких ефектів. Тоді UI залишається читабельним і впізнаваним в будь-якій обстановці - від нічного лайв-матчу до денної каси і турнірних екранів.