Темы
На этой странице
Все компоненты читают одни и те же кастомные свойства, поэтому тема — это просто набор переопределений на :root: ни шага сборки, ни файла конфигурации, ни компонента, которому пришлось бы об этом сообщать.
Подключить стили
styles() возвращает <link> на один файл, который браузер кэширует на всех страницах сайта. Настраивать нечего и копировать нечего: плагин sitelo отдаёт его в dev и записывает в сборку — по тому же базовому пути, что и рантайм компонентов.
import { styles } from 'sitelo/ui'
head(
title('Мой сайт'),
styles(),
)
// <link rel="stylesheet" href="/su/ui-c9428b65.css">В имени файла — хеш содержимого, так что его можно отдавать с immutable и при этом выкатывать изменения. Передайте { hash: false }, если нужен просто /su/ui.css, или base, чтобы указать ссылку на копию, которую вы размещаете сами.
{ inline: true } вместо этого кладёт всю таблицу в <style>: около 11 кБ в gzip на каждой странице, зато ни одного лишнего запроса и ничего, что могло бы пропасть из dist/. Для одиночной страницы это выгоднее; ссылка отбивает свой запрос на второй прочитанной странице.
head(
title('Мой сайт'),
styles({ inline: true }),
)Остальные функции семейства выдают детали по отдельности. stylesheet() возвращает сырой CSS строкой — чтобы разместить таблицу там, куда sitelo не дотянется, или записать её куда-нибудь самому, — а stylesUrl() — только URL, для вашего собственного элемента link.
Пресеты
Пресет меняет весь облик разом. styles({ preset: 'neumorphism' }) подключает вторую таблицу стилей сразу после основной — она отдаётся, хешируется и кешируется на тех же условиях, — и каждый компонент на странице следует за ней без единой правки в разметке.
import { styles } from 'sitelo/ui'
head(
title('Мой сайт'),
styles({ preset: 'neumorphism' }),
)
// <link rel="stylesheet" href="/su/ui-c9428b65.css">
// <link rel="stylesheet" href="/su/neumorphism-5d0e7b91.css">У каждого пресета есть своя страница, где все компоненты переоформлены вживую: Неоморфизм, Необрутализм, Superneon.
inline встраивает обе таблицы, stylesheet({ preset }) возвращает их одной строкой, а имя, которое не является пресетом, выбрасывает ошибку со списком существующих.
Переопределение токенов
theme() записывает переопределения. Ключи — это имена токенов в camelCase, объекты палитр или буквальные кастомные свойства, а идёт всё это после styles(), так что побеждает.
import { styles, theme } from 'sitelo/ui'
head(
styles(),
theme({
primary: { base: '#5b5bd6', hover: '#4a4ac4', active: '#3f3fb0', fg: '#ffffff' },
radiusMd: '2px',
fontSans: '"Inter", system-ui, sans-serif',
}),
)Темы с областью действия
selector ограничивает переопределения поддеревом, а не всей страницей. Именно это и делают три панели ниже: одни и те же компоненты, три разные палитры, одна страница.
fragment(
theme({ primary: { base: '#5b5bd6', hover: '#4a4ac4', fg: '#ffffff', soft: '#e6e6fa', softFg: '#33338f', border: '#b9b9ee' } }, { selector: '.theme-indigo' }),
theme({ primary: { base: '#b0357a', hover: '#962e68', fg: '#ffffff', soft: '#fbe4f0', softFg: '#7d1f53', border: '#f0a9ce' } }, { selector: '.theme-pink' }),
theme({ radiusMd: '999px', radiusLg: '1.5rem' }, { selector: '.theme-round' }),
grid({ min: '11rem' },
div({ class: 'theme-indigo' },
card(cardBody(stack({ gap: 'sm' },
text({ variant: 'caption', tone: 'muted' }, 'индиго'),
button({ block: true }, 'Primary'),
button({ variant: 'soft', block: true }, 'Soft'),
))),
),
div({ class: 'theme-pink' },
card(cardBody(stack({ gap: 'sm' },
text({ variant: 'caption', tone: 'muted' }, 'розовая'),
button({ block: true }, 'Primary'),
button({ variant: 'soft', block: true }, 'Soft'),
))),
),
div({ class: 'theme-round' },
card(cardBody(stack({ gap: 'sm' },
text({ variant: 'caption', tone: 'muted' }, 'скруглённая'),
button({ block: true }, 'Primary'),
button({ variant: 'soft', block: true }, 'Soft'),
))),
),
),
)Тёмная тема
Тёмная определяется сама по prefers-color-scheme. Явный data-theme или data-su-theme со значением light или dark на любом предке это перебивает — именно так демо на этом сайте следуют за переключателем в верхней панели.
Передайте dark для переопределений, которые должны действовать только там. Это разом закрывает и атрибут, и медиазапрос.
theme({
primary: { base: '#5b5bd6' },
}, {
dark: { primary: { base: '#8f8ff0' } },
})Что можно переопределить
Пять палитр по девять слотов, шкала интервалов, типографика, скругления, тени и цвета поверхностей. Каждое из этого — кастомное свойство: откройте таблицу стилей или инспектор браузера, и все они окажутся на :root.
stack({ gap: 'md' },
stack({ direction: 'row', gap: 'sm', wrap: true },
...['primary', 'neutral', 'success', 'warning', 'danger'].map((color) =>
stack({ gap: 'xs', align: 'center' },
div({ style: 'width: 3.5rem; height: 2rem; border-radius: 0.4rem; background: var(--su-' + color + ')' }),
text({ variant: 'caption', tone: 'muted' }, color),
),
),
),
stack({ direction: 'row', gap: 'sm', wrap: true, align: 'flex-end' },
...['xs', 'sm', 'md', 'lg', 'xl'].map((step) =>
stack({ gap: 'xs', align: 'center' },
div({ style: 'width: var(--su-space-' + step + '); height: 2rem; border-radius: 0.2rem; background: var(--su-neutral)' }),
text({ variant: 'caption', tone: 'muted' }, step),
),
),
),
)Именование
Ключ в camelCase превращается в свойство в kebab-case: radiusMd — это --su-radius-md, а fontSans — --su-font-sans. Вложенный объект разворачивается так же: { primary: { softFg: … } } задаёт --su-primary-soft-fg. А ключ, уже начинающийся с --, берётся ровно так, как написан, — это запасной выход для всего, чего преобразование не покрывает.
В палитре девять слотов: base, hover, active, fg, soft, softHover, softFg, border и ring. Задавайте только те, что меняете.
Контраст
Поставляемые палитры проходят WCAG AA относительно поверхностей, на которых лежат, в обеих темах, и в репозитории есть тест, который валит сборку, если это перестанет быть правдой. На вашу собственную тему он не распространяется: сверьте свой fg со своим base, прежде чем выкатывать её.
Пропсы
styles():
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
preset | 'neumorphism' | 'neubrutalism' | 'superneon' | — | Переоформить все компоненты пресетом, подключённым или встроенным после основной таблицы. |
inline | boolean | false | Выдать сам CSS, а не ссылку на него. |
hash | boolean | true | Добавить в имя файла хеш содержимого. Только для ссылки. |
base | string | '/su/' | Указывает URL в другое место; этот файл размещаете вы сами. Только для ссылки. |
minify | boolean | true | Убрать комментарии и пробелы. Только для inline. |
nonce | string | — | CSP-nonce для выдаваемого элемента. |
stylesUrl() принимает base и hash; stylesheet() — minify и preset.
theme(tokens, options):
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
selector | string | ':root' | Ограничивает переопределения поддеревом. |
dark | object | — | Переопределения, действующие только в тёмной теме. |
nonce | string | — | CSP-nonce. |