Урок 13. Решения
⚡ Кратко
Три решения: карточка с dark:, она же на токенах и код
переключателя без мигания.
Решение задания 1
Отчёт готов
Данные обновлены пять минут назад.
<div class="max-w-sm rounded-xl border border-slate-200 bg-white p-5 shadow-md
dark:border-slate-700 dark:bg-slate-800 dark:shadow-none dark:ring-1 dark:ring-white/10">
<h3 class="text-sm font-semibold text-slate-900 dark:text-slate-100">Отчёт готов</h3>
<p class="mt-1 text-sm text-slate-600 dark:text-slate-400">Данные обновлены пять минут назад.</p>
<button class="mt-4 rounded-md bg-slate-900 px-3 py-2 text-sm font-medium text-white hover:bg-slate-700
dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white">Открыть</button>
</div>
Почему так: в тёмной теме основная кнопка меняется местами по контрасту: светлая кнопка на тёмном фоне. Слепое «сделать её темнее» сделало бы её невидимой.
Решение задания 2
📄 src/index.css
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
@theme inline {
--color-surface: var(--surface);
--color-surface-foreground: var(--surface-foreground);
--color-subtle: var(--subtle);
--color-line: var(--line);
}
:root {
--surface: oklch(1 0 0);
--surface-foreground: oklch(0.21 0.01 265);
--subtle: oklch(0.55 0.02 265);
--line: oklch(0.92 0.01 265);
}
.dark {
--surface: oklch(0.27 0.01 265);
--surface-foreground: oklch(0.98 0 0);
--subtle: oklch(0.72 0.02 265);
--line: oklch(0.37 0.01 265);
}
<div class="max-w-sm rounded-xl border border-line bg-surface p-5 text-surface-foreground">
<h3 class="text-sm font-semibold">Отчёт готов</h3>
<p class="mt-1 text-sm text-subtle">Данные обновлены пять минут назад.</p>
</div>
Почему так: имена описывают роль, а не цвет. Через полгода, когда дизайнер попросит «сделать поверхности чуть теплее», правка будет в четырёх строках, а не в четырёхстах классах.
Решение задания 3
<!-- index.html — до подключения бандла -->
<script>
(() => {
const saved = localStorage.getItem('theme') ?? 'system'
const dark = saved === 'dark' ||
(saved === 'system' && matchMedia('(prefers-color-scheme: dark)').matches)
document.documentElement.classList.toggle('dark', dark)
})()
</script>
// src/theme.ts — переключение темы и запоминание выбора
const KEY = 'theme'
export function applyTheme(theme: 'light' | 'dark' | 'system') {
const isDark =
theme === 'dark' ||
(theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
document.documentElement.classList.toggle('dark', isDark)
localStorage.setItem(KEY, theme)
}
// вызывать как можно раньше, до первой отрисовки
applyTheme((localStorage.getItem(KEY) as 'light' | 'dark' | 'system') ?? 'system')
Почему так: синхронный скрипт выполняется до отрисовки, поэтому первый кадр уже правильный. Всё остальное — обычная логика переключателя. Не забудьте подписаться на изменение системной темы, если выбран режим «системная»:
matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
if ((localStorage.getItem('theme') ?? 'system') === 'system') applyTheme('system')
})