Тема — переменные стиля на всё приложение
Одна таблица переменных на всё приложение, разрешаемая вживую ядром UI. theme(values) сливает
переменные в неё и возвращает типизированные аксессоры, чьи значения и есть строки var(); любое
значение стиля может ссылаться на переменную, а повторный вызов theme() перестилизует
работающий UI на месте — тёмная тема, смена бренда или персональный акцент — это ещё один
вызов, а не пересборка. Эта страница — канонический дом theme(); словарь свойств, в которые
переменные подставляются, — в styling.md.
Обзор
Стандартная форма — маленький модуль токенов, который импортирует каждый экран: палитра в
theme(), шкалы — обычные константы:
// theme.ts — определи один раз, экспортируй аксессоры
const palette = {
bg: "#F4F6F5", card: "#FFFFFF", border: "#E4E8E6",
text: "#131A17", muted: "#606B65",
accent: "#15A34A", accentSoft: "#E7F6ED", onAccent: "#FFFFFF",
}
// Системные ключи: цвет текста по умолчанию + чем красят себя кит-компоненты (панель UITabs).
theme({ color: palette.text, primaryColor: palette.accent, mutedColor: palette.muted })
export const colors: { [K in keyof typeof palette]: string } = theme(palette)
export const font = { // шкала типографики — распыляй в стили: .style({ ...font.h2 })
h2: { fontSize: 22, fontWeight: 700 }, body: { fontSize: 16 },
small: { fontSize: 14 }, tiny: { fontSize: 12, fontWeight: 500 },
}// любой экран
import { colors, font } from './theme'
const card = UIColumn(
UIText("Total").style({ ...font.small, color: colors.muted }),
UIText("$1,240").style({ ...font.h2 }),
).style({ bgColor: colors.card, border: `1px solid ${colors.border}`, borderRadius: 16, p: 16 })// позже, откуда угодно — каждый стиль, ссылающийся на токен, перекрашивается на месте
theme({ bg: "#0C0E13", card: "#161A23", text: "#EEF1F8", border: "#2A3040" }) // тёмная темаcolors.card — это буквально строка "var(--card)" — хост разрешает её в момент применения стиля.
Шкалы отступов и типографики остаются обычными TS-константами: переменная темы — для того, что
должно уметь меняться, пока приложение работает.
экспорт аксессоров аннотирован { [K in keyof typeof palette]: string } не случайно. theme()
выводит литеральные типы ("var(--accent)"), и хелпер вида (color = colors.accent) иначе сузил бы
свой параметр до этого одного токена.
Определение переменных
theme(values): accessors // СЛИВАЕТ в живую таблицу; неупомянутые ключи сохраняют значения- Строки проходят как есть — цвета, имена шрифтов, целые выражения (
"max(safe-top, 24px)"). - Числа — длины (логические px).
nullудаляет ключ (ручки комфорта сбрасываются к встроенным дефолтам).- Env-имена (
safe-top…,vw/vh/vmin/vmax) — факты хоста, никогда не ключи темы — записи пропускаются с предупреждением в консоли. - Один нестилевой ключ:
replaceTransitionниже.
Каждый вызов возвращает аксессоры для определённых им ключей; возвращённый объект — всё, что нужно экспортировать из модуля токенов.
Чтение переменных
label.style({ color: T.accent }) // аксессор — T.accent === "var(--accent)"
label.style({ color: "var(--accent)" }) // сырая строковая форма — то же самое
icon.style({ tintColor: "var(--accent, #0A84FF)" }) // фолбэк действует, пока ключ не задан
list.style({ pl: "calc(var(--comfort-left) * 2)" }) // компонуется в calc()/min()/max()var() работает в любом значении UI-стиля и больше нигде — что это исключает, см.
Подводные камни. var() для ключа, который никогда не определялся, разрешается в свой
фолбэк или в ничто.
Системные переменные
Некоторые ключи читает сам SDK, а не только твои стили. Они заранее типизированы как статические
свойства глобала — theme.color, theme.primaryColor, … — так что модуль токенов им не нужен.
Текст по умолчанию — два ключа управляют каждым UIText, который не задал собственное значение,
так что светлая тема — это одна строка вместо color: "black" на каждой подписи:
theme({ color: "#1C1C1E", fontFamily: font("manrope") }) // весь нестилизованный текст следуетНе заданные, они равны дефолтам хоста (белый текст, шрифт хоста). Собственные color /
fontFamily элемента всегда побеждают. Фон экрана остаётся у каждого экрана свой (bgColor) —
стилизуй его явно.
Кит-компоненты — UITabs стилизует свою панель целиком через переменные
темы (с тёмными фолбэками, так что приложение без темы всё равно выглядит правильно):
| Ключ | Управляет |
|---|---|
primaryColor |
иконка + подпись активного таба (и общий «акцент приложения» для кит-компонентов) |
mutedColor |
иконка + подпись неактивного таба / вторичный контент |
tabbarBg |
поверхность таб-бара |
tabbarBorder |
верхняя волосяная линия таб-бара |
screenBg |
фон по умолчанию за экранами табов |
badgeColor |
точка бейджа / пилюля счётчика |
Ручки комфорта — "comfort-top" / "comfort-bottom" / "comfort-left" / "comfort-right"
настраивают комфортные токены безопасных зон (только
простые длины):
theme({ "comfort-left": 20, "comfort-right": 20 }) // гаттер страниц этого приложения — 20голый токен стиля (pt: "comfort-top") применяет формулу безопасной зоны; var(--comfort-top)
читает сырое значение ручки (по умолчанию 12) для ручной композиции.
Живая смена темы
Поскольку каждый var() переразрешается на месте, вызов theme() перестилизует то, что сейчас на
экране, — без пересборки экрана, без протаскивания пропсов, без точки вызова, которую можно забыть.
Поэтому таблица — правильный дом для всего, что решается в рантайме:
// Тёмная тема: второй вызов.
const setDark = (dark: boolean) => theme(dark ? darkPalette : lightPalette)
// Акцент с сервера: выведи несколько переменных из одного hex и перезапиши их.
// Каждый стиль, где написано var(--accent) / var(--accentSoft), следует — включая экраны,
// которые уже смонтированы.
const applyBrand = (hex: string | null) =>
theme(hex ? { accent: hex, accentSoft: soften(hex) } : { accent: "#15A34A", accentSoft: "#E7F6ED" })Записи применяются сразу, когда бы ни случились — между вызовом theme() и экранами, построенными
до или после него, нет опасности порядка.
replaceTransition
Один ключ потребляется роутером SDK и не попадает в таблицу переменных: общий для приложения переход
по умолчанию для Router.replace (из коробки "none").
theme({ replaceTransition: "fade" }) // любое имя перехода или свой спек; null сбрасываетЯвный Router.replace(screen, { transition }) на вызов всё равно побеждает. Аксессор не
возвращается — var(--replaceTransition) не значение стиля. См.
screen-router.md.
Подводные камни
// ✗ var() вне UI-стилей — SVG-XML, цвета движка, параметры нативных вью получают буквальную строку
SvgSource(`<circle fill="${colors.accent}" ...>`) // не рендерит ничего полезного
sprite.color = colors.accent // API движка парсят только hex/упакованный int
// ✓ держи сырой объект палитры экспортированным рядом с аксессорами для этих потребителей
SvgSource(`<circle fill="${palette.accent}" ...>`)
// ✗ темизация env-имени — принадлежит хосту, пропускается с предупреждением
theme({ "safe-top": 0 })
// ✗ формула в ручке комфорта — ручки принимают только простые длины
theme({ "comfort-bottom": "max(safe-bottom, 16px)" })
// ✓ пиши формулу инлайн в стиле или как собственную переменную
theme({ tabInset: "max(safe-bottom, 16px)" }); bar.style({ pb: "var(--tabInset)" })Смотрите также
- Стилизация — словарь свойств и формы значений, куда подставляются переменные.
- Классы стилей — именованные состояния на элемент (тема = на всё приложение, классы = на элемент).
- Шрифты —
theme({ fontFamily: font(...) }), механизм шрифта по умолчанию. - UIPager и UITabs — таб-бар, который стилизуют кит-переменные.