Храните общие токены дизайна в CSS страницы, предоставляйте специфичные для компонента переменные как API стилизации и связывайте их небольшой таблицей соответствий, принадлежащей странице. В этом руководстве создаётся локальный демо-проект для Mac, в котором эти обязанности разделены.
Что относится к странице, а что — к компоненту?
Для тех, кто редактирует созданную вручную веб-страницу на Mac, конкретная задача — задать тему переиспользуемому компоненту, не привязывая его реализацию к названиям токенов страницы. В примере ниже используется синтетический компонент orbit-tile, содержащий абстрактную фигуру и короткую подпись.
И токен дизайна, и общедоступная переменная компонента могут быть пользовательскими CSS-свойствами. Их различие определяется документируемым контрактом. Обычные незарегистрированные пользовательские свойства участвуют в каскаде и наследуются; объявление их в :root делает их доступными через наследование во всём документе. В CSS нет специального ключевого слова, которое превращает свойство в токен дизайна. Использование пользовательских CSS-свойств (переменных)
Используйте эту таблицу владения, чтобы решить, где должно находиться объявление. Названия приведены для примера и используются только здесь.
| Слой | Пример | Назначение |
|---|---|---|
| Токены дизайна страницы | --scene-accent, --scene-rounding |
Общие визуальные решения для созданной страницы |
| Правила интеграции страницы | Правило для orbit-tile |
Перевод решений страницы в API компонента |
| Общедоступные переменные компонента | --orbit-tile-fill, --orbit-tile-radius |
Документированные настройки, доступные потребителям |
| Реализация компонента | .shape, правила компоновки, буквальные значения по умолчанию |
Отрисовка компонента без зависимости от токенов страницы |
Полезная проверка — переносимость: может ли другая страница использовать те же названия токенов, не меняя компонент? При таблице соответствий, которой владеет страница, может. Новая страница сопоставит собственные токены с теми же общедоступными переменными компонента.
Как контракт стилизации проходит через Shadow DOM?
Shadow DOM разделяет сопоставление селекторов и наследование. Селектор страницы обычно не может выбрать внутреннюю фигуру через границу теневого DOM. Однако унаследованные значения свойств по-прежнему могут попасть в неё через хост. Спецификация CSS Shadow описывает уплощённое дерево элементов, используемое для этой связи; отдельные деревья селекторов не отменяют наследование. Модуль CSS Shadow, уровень 1
Оставляйте общедоступные переменные незаданными внутри компонента и указывайте значения по умолчанию там, где они используются. Например, background-color: var(--orbit-tile-fill, #667085) принимает унаследованное значение потребителя и при этом задаёт значение по умолчанию для автономного использования. Если вместо этого объявить --orbit-tile-fill: #667085 на :host, хост получит собственное значение, которое перекроет унаследованное от предка. Потребитель всё ещё сможет обратиться непосредственно к хосту. Стилизация веб-компонентов
В этом примере документируются только два общедоступных параметра:
| Общедоступная переменная | Допустимое значение в этом примере | Значение по умолчанию, если переменная не задана | Эффект |
|---|---|---|---|
--orbit-tile-fill |
Допустимый цвет CSS | #667085 |
Цвет фона фигуры |
--orbit-tile-radius |
Одна неотрицательная длина или процент | 1rem |
Радиус скругления углов фигуры |
Эти допустимые значения — договорённость API. Базовая реализация не регистрирует их и не проверяет автоматически. Внутренние имена классов — детали реализации, поэтому авторам страниц следует настраивать общедоступные переменные, а не полагаться на .shape.
Создайте полный локальный пример на Mac
Используйте текстовый редактор и браузер с поддержкой пользовательских CSS-свойств, Shadow DOM и автономных пользовательских элементов. В этом примере используется автономный элемент, расширяющий HTMLElement; MDN отличает такой подход от настраиваемых встроенных элементов, поддержку которых Safari не планирует. Использование пользовательских элементов
В TextEdit выберите File > New, затем Format > Make Plain Text. Вставьте весь документ ниже и сохраните его как token-demo.html. Если появится запрос, выберите Use .html. Чтобы снова открыть файл для редактирования, выберите Ignore rich text commands в диалоговом окне открытия или включите параметр Display HTML files as HTML code instead of formatted text в настройках. Работа с HTML-документами в TextEdit на Mac
Первая таблица стилей документа отвечает за токены, правила интеграции и компоновку страницы. Компонент создаёт отдельную таблицу стилей внутри своей теневой области. HTML определяет элемент style как средство встраивания CSS; размещение всего кода в одном файле позволяет явно показать настройку ресурсов в этом примере. Стандарт HTML: элемент style
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Orbit tile token demo</title>
<style>
/* Page-owned shared tokens */
:root {
--scene-accent: #4058b8;
--scene-rounding: 12px;
}
/* Page-owned integration mapping */
orbit-tile {
--orbit-tile-fill: var(--scene-accent);
--orbit-tile-radius: var(--scene-rounding);
}
/* A local page theme */
.alternate {
--scene-accent: #276f64;
--scene-rounding: 24px;
}
/* An intentional override for one component instance */
orbit-tile.special {
--orbit-tile-fill: #a65b20;
--orbit-tile-radius: 50%;
}
body {
max-width: 36rem;
margin: 3rem auto;
padding: 0 1rem;
font: 1rem/1.5 system-ui, sans-serif;
}
main { display: grid; gap: 1rem; }
</style>
</head>
<body>
<main>
<orbit-tile>Sample A</orbit-tile>
<section class="alternate" aria-label="Alternate samples">
<orbit-tile>Sample B</orbit-tile>
</section>
<orbit-tile class="special">Sample C</orbit-tile>
</main>
<script>
class OrbitTile extends HTMLElement {
constructor() {
super();
const root = this.attachShadow({ mode: 'open' });
const style = document.createElement('style');
style.textContent = `
:host { display: block; }
.tile {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
background: #f2f4f6;
color: #202630;
}
.shape {
display: block;
width: 4rem;
height: 4rem;
flex: 0 0 4rem;
background-color: var(--orbit-tile-fill, #667085);
border-radius: var(--orbit-tile-radius, 1rem);
}
`;
const tile = document.createElement('div');
tile.className = 'tile';
const shape = document.createElement('span');
shape.className = 'shape';
shape.setAttribute('aria-hidden', 'true');
tile.append(shape, document.createElement('slot'));
root.append(style, tile);
}
}
customElements.define('orbit-tile', OrbitTile);
</script>
</body>
</html>
Откройте сохранённый файл в Safari, Chrome или Firefox. В примере нет внешних скриптов, шрифтов, изображений или таблиц стилей. Синтаксис JavaScript прошёл проверку; приведённые ниже визуальные результаты ожидаются на основании объявлений и не являются отчётом о запуске в браузере.
| Экземпляр | Ожидаемый вид фигуры | Путь значения |
|---|---|---|
Sample A |
Синяя, со скруглением углов 12px |
Корневые токены → сопоставление на хосте → компонент |
Sample B |
Зелёная, со скруглением углов 24px |
Токены секции → сопоставление на хосте → компонент |
Sample C |
Оранжевый круг | Переопределение экземпляра → компонент |
Обратите внимание: в JavaScript компонента нет ссылок на --scene-*. Его таблица стилей использует только общедоступные имена --orbit-tile-*. Страница может перестроить словарь своей темы, не меняя компонент.
Где сопоставлять токены с переменными компонента?
Сопоставляйте значения там, где доступна нужная тема. В этом демо правило orbit-tile применяется к каждому хосту. Благодаря этому второй хост получает значение --scene-accent после наследования зелёного значения из секции.
Не переносите это сопоставление в :root, не учитывая порядок вычисления. Если --orbit-tile-fill: var(--scene-accent) вычисляется там, потомки наследуют его вычисленное значение. Изменение только --scene-accent в последующей секции не пересоздаёт унаследованный псевдоним. Ссылки var() в пользовательских свойствах обрабатываются до наследования вычисленных значений. Модуль пользовательских CSS-свойств для каскадных переменных, уровень 1
Практический критерий — область действия: используйте сопоставление на хосте, если каждый экземпляр должен следовать токенам окружающей страницы. Если для целого раздела намеренно задано общее значение переменной компонента, эту общедоступную переменную можно объявить на разделе, но удалите сопоставление на хосте для неё, чтобы оно не заменяло унаследованное значение.
Для многократного использования внутри компонента псевдоним реализации может объединить резервные объявления. Имя вроде --_orbit-tile-fill может обозначать внутреннее соглашение, но подчёркивание не создаёт реальной защиты от доступа. Прямое использование, как в полном примере, проще проследить, когда каждое значение применяется один раз. Стилизация веб-компонентов
Как диагностировать неработающее переопределение?
Используйте три экземпляра как небольшой тест контракта. Изменяйте только одно объявление за раз, сохраняйте файл и перезагружайте страницу. Эти проверки помогают отличить отсутствие ресурса от проблемы наследования и недопустимого значения.
- Измените корневой акцент на
#7251a1. Ожидается, что его примет толькоSample A: для секции и экземпляра заданы собственные значения. - Измените акцент секции на
#32758a. Ожидается, что изменитсяSample B, а код компонента останется нетронутым. - Удалите таблицу интеграции целиком. Ожидается, что
Sample AиSample Bбудут использовать серую заливку и радиус1remкомпонента; явные общедоступные переопределенияSample Cсохранятся. - Восстановите таблицу соответствий, прежде чем продолжать эксперименты.
Если все фигуры исчезнут, сначала проверьте, что документ сохранён в формате HTML и скрипт не повреждён. Если подписи видны, но фигуры не оформлены, проверьте таблицу стилей компонента. Одна лишь таблица стилей документа не может обычными селекторами оформлять потомков теневого DOM; реализация должна добавить внутренние стили в теневое дерево. MDN описывает для этого как встроенные стили, так и создаваемые таблицы стилей. Использование Shadow DOM
Если не срабатывает только переопределение предка, проверьте, объявлена ли общедоступная переменная непосредственно на хосте. В этом примере таким объявлением служит таблица интеграции. Измените токен страницы, который подаёт ей значение, переопределите общедоступную переменную для экземпляра или удалите сопоставление, если контракт подразумевает настройку темы через общедоступные значения на уровне предка.
Если фигура становится прозрачной после присваивания --orbit-tile-fill: 12px, резервное значение не поможет. Переменная существует, но подставленное значение недопустимо для background-color. Объявление становится недопустимым на этапе вычисления значения; возврата к более раннему объявлению фона не происходит. Удалите присваивание или укажите допустимый цвет. Модуль пользовательских CSS-свойств для каскадных переменных, уровень 1
Следует ли использовать @property для общедоступных переменных?
Регистрация для этого контракта необязательна. @property добавляет ограничения синтаксиса, начальное значение и явное поведение наследования. По данным MDN, правило относится к Baseline 2024 и доступно во всех современных браузерах с июля 2024 года, однако более старые браузеры могут его не поддерживать. Полный пример не требует регистрации для работы. CSS-правило @property
Если вы добавляете регистрацию, согласуйте её на уровне документа. В документации Chrome по реализации сказано, что регистрации внутри теневых корней игнорируются; её отчёт датирован августом 2024 года, поэтому считайте это задокументированной границей совместимости, а не результатом новой проверки браузеров. В документе описана регистрация в области видимости документа, при которой компонент использует свойство через границу. Имена CSS, заданные автором, и Shadow DOM: в спецификации и на практике
Регистрация также меняет поведение. Значение inherits: false нарушило бы наследование от хоста к фигуре в этом примере. Зарегистрированный начальный цвет означает, что незаданное свойство получит этот цвет, и подстановка var() уже не будет значением по умолчанию для отсутствующего значения. Для этой схемы оставьте inherits: true и заранее решите, какой механизм будет задавать значение по умолчанию. CSS-правило @property
Готовый контракт должен позволять автору страницы объяснить путь каждого значения: общее решение страницы, его сопоставление с документированной настройкой компонента и резервное значение, заданное компонентом. Размещайте сопоставление рядом с предполагаемой областью действия темы, оставляйте наследуемые входные значения незаданными внутри компонента и сохраняйте пример с тремя экземплярами при каждом изменении API.
Источники
- Использование пользовательских CSS-свойств (переменных) — MDN Web Docs.
- Модуль CSS Shadow, уровень 1 — Рабочая группа W3C по CSS.
- Стилизация веб-компонентов — Chrome для разработчиков.
- Использование пользовательских элементов — MDN Web Docs.
- Работа с HTML-документами в TextEdit на Mac — Поддержка Apple.
- Стандарт HTML — WHATWG.
- Модуль пользовательских CSS-свойств для каскадных переменных, уровень 1 — W3C.
- Использование Shadow DOM — MDN Web Docs.
- CSS-правило @property — MDN Web Docs.
- Имена CSS, заданные автором, и Shadow DOM: в спецификации и на практике — Chrome для разработчиков.