Урок 13. Примеры: интерфейс в двух темах

📁 Раздел: Состояния и темы ⏱️ Время изучения: ~50 мин 🎯 Сложность: Средняя

⚡ Кратко

Пять примеров в двух темах сразу — переключите тему страницы (🌙) и сравните.

1. Шапка приложения

Панель
<header class="flex items-center justify-between rounded-lg border border-slate-200 bg-white px-4 py-3
               dark:border-slate-700 dark:bg-slate-900">
  <span class="text-sm font-semibold text-slate-900 dark:text-slate-100">Панель</span>
  <nav class="flex gap-4 text-sm text-slate-600 dark:text-slate-400">
    <a class="hover:text-slate-900 dark:hover:text-slate-100" href="#">Обзор</a>
    <a class="hover:text-slate-900 dark:hover:text-slate-100" href="#">Отчёты</a>
  </nav>
</header>

2. Та же шапка на токенах

Панель
<header class="flex items-center justify-between rounded-lg border border-border bg-card px-4 py-3 text-card-foreground">
  <span class="text-sm font-semibold">Панель</span>
  <nav class="flex gap-4 text-sm text-muted-foreground">
    <a class="hover:text-foreground" href="#">Обзор</a>
    <a class="hover:text-foreground" href="#">Отчёты</a>
  </nav>
</header>

Классов вдвое меньше, а поведение то же. Разница станет решающей, когда таких блоков в проекте будет полсотни.

3. Форма

<form class="max-w-sm space-y-3 rounded-xl border border-border bg-card p-4 text-card-foreground">
  <div>
    <label class="mb-1 block text-sm font-medium" for="dk-mail">Почта</label>
    <input id="dk-mail" class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground
                               placeholder:text-muted-foreground focus:ring-2 focus:ring-ring/40 focus:outline-none"
           placeholder="name@example.com">
  </div>
  <div>
    <label class="mb-1 block text-sm font-medium" for="dk-pass">Пароль</label>
    <input id="dk-pass" type="password" class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground
                                               focus:ring-2 focus:ring-ring/40 focus:outline-none" placeholder="••••••••">
  </div>
  <button class="w-full rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground hover:opacity-90">Войти</button>
</form>

4. Статусы в двух темах

Прозрачность работает лучше, чем подбор двух наборов цветов: один и тот же класс выглядит уместно на светлом и тёмном фоне.

Готово Ожидает Ошибка
<div class="flex flex-wrap gap-2">
  <span class="rounded-full bg-emerald-500/15 px-2.5 py-0.5 text-xs font-medium text-emerald-700 dark:text-emerald-300">Готово</span>
  <span class="rounded-full bg-amber-500/15 px-2.5 py-0.5 text-xs font-medium text-amber-700 dark:text-amber-300">Ожидает</span>
  <span class="rounded-full bg-rose-500/15 px-2.5 py-0.5 text-xs font-medium text-rose-700 dark:text-rose-300">Ошибка</span>
</div>

5. Переключатель темы (внешний вид)

<div class="inline-flex rounded-lg border border-border bg-card p-1 text-sm">
  <button class="rounded-md px-3 py-1.5 text-muted-foreground hover:text-foreground">☀️ Светлая</button>
  <button class="rounded-md bg-primary px-3 py-1.5 text-primary-foreground">🌙 Тёмная</button>
  <button class="rounded-md px-3 py-1.5 text-muted-foreground hover:text-foreground">💻 Системная</button>
</div>
// 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')

6. Сравнение поверхностей

Уровни поверхности в обеих темах

Карточка (bg-card)
Приглушённая область (bg-muted)
Акцентная область (bg-accent)
<div class="space-y-3 rounded-xl border border-border bg-background p-4">
  <p class="text-sm text-muted-foreground">Уровни поверхности в обеих темах</p>
  <div class="rounded-lg border border-border bg-card p-3 text-sm text-card-foreground">Карточка (bg-card)</div>
  <div class="rounded-lg bg-muted p-3 text-sm text-muted-foreground">Приглушённая область (bg-muted)</div>
  <div class="rounded-lg bg-accent p-3 text-sm text-accent-foreground">Акцентная область (bg-accent)</div>
</div>