Перейти до вмісту
tatetUI

Темна тема

Клас на html, блокуючий скрипт проти спалаху і чому inline в @theme обов'язковий.

Тема перемикається класом .dark на <html>, а не медіазапитом. Медіазапит не дав би користувачеві вибору — а вибір потрібен: ту саму систему вмикають і вдень біля вікна, і вночі.

Чому компоненти не мають dark:-префіксів

Утиліти посилаються на CSS-змінні, а .dark перевизначає самі змінні. Тому bg-card працює в обох темах без жодного dark:bg-....

Це тримається на одному слові в theme.css:

@theme inline {
  --color-card: var(--bg-card);
}

Без inline Tailwind підставив би значення змінної в момент генерації, і bg-card назавжди застигла б білою. Це найлегша помилка в усій системі й найважча для діагностики: усе виглядає правильно, поки не перемкнеш тему.

Спалах при завантаженні

Сторінки прередерені, і в їхньому HTML класу .dark немає — його ставить лише клієнт. Без запобіжника користувач із темною темою бачив би білий спалах на кожному завантаженні, поки не відпрацює гідрація.

Тому в <head> стоїть блокуючий скрипт, який виконується до першого малювання:

(() => {
  try {
    const s = localStorage.getItem('tatetui-theme')
    const d = s ? s === 'dark' : matchMedia('(prefers-color-scheme:dark)').matches
    if (d) document.documentElement.classList.add('dark')
  } catch (e) {}
})()

try/catch обов'язковий: у приватному режимі Safari звернення до localStorage кидає виняток, і без нього сторінка не намалювалася б узагалі.

Джерело правди — DOM, а не сховище

useTheme() читає початкове значення з класу на <html>, а не з localStorage. Клас уже поставив скрипт вище, і саме він — джерело правди.

Якби composable читав сховище сам, сервер і клієнт розійшлися б у першому рендері прередереної сторінки, і Vue лаявся б на невідповідність гідрації.

Що перевіряти в кожному компоненті

Тіні. У світлій темі --shadow-card це 4% чорного — на темному тлі такого просто не видно, і картка «злипається» з фоном. Тому в темній темі тіні сильніші, і це задано токеном, а не класом.

Напівпрозорі підкладки. bg-primary-900/30 над майже чорним тлом дає видимий результат лише тому, що --brand-900 у темній темі світліший. Якщо додаєте власний відтінок — перевірте його саме в такому вживанні.