Эта статья — перевод оригинальной статьи "Type safe CSS design systems with @property".

Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.

Вступление

Типы CSS - это достойное вложение в безопасность типов при работе с внешним интерфейсом. Мы все еще ожидаем кроссбраузерности, но мы к этому придем ???? .

Если вы никогда не видели типизированную CSS-переменную с @property, то вот пример:

@property --focal-size {
  syntax: '<length-percentage>';
  initial-value: 100%;
  inherits: false;
}

Я использовал её, чтобы анимировать изображение с градиентной маской. Довольно мило.

Вот предварительный обзор того, что может сделать безопасность типов CSS, и того, что я буду объяснять:

Основы безопасности CSS типов

При изучении Rust или TypeScript лучше всего начать с примитивов типов. В CSS их несколько:

Больше типов на MDN и полный список грамматик и типов на csswg.org/indexes/#types.

Еще одно определение переменной:

@property --hue {
  syntax: '<angle>';
  initial-value: .5turn;
  inherits: false;
}

Используйте её так же, как и var(--hue), и она будет равна .5turn. Но попробуйте установить его в значение, не соответствующее её типу? Не получится, значение по-прежнему будет равно .5turn. Переменная не позволит присвоить себе значение, не соответствующее её типу, всегда возвращаясь к последнему подходящему значению.

.card {
  --hue: 90deg; /* ✅ */
  --hue: #f00;  /* ❌ */
  background: oklch(98% .01 var(--hue));

  /* background will always resolve ???????? */
  /* --hue resolves 90deg *.
}

Это безопасность CSS типов. Она не приводит к краху страницы, не блокирует поток и, к сожалению, не сообщает в консоли о том, что была попытка установить для свойства --hue значение <color>, а не <angle>. Но я думаю, что более качественный инструментарий мог бы помочь ????.

Уровень 2

Пока что я создал переменную как <angle> и использовал её в свойстве background. Никакой вложенности свойств.

Перейдем на более глубокий уровень, сделав переменную использующую другую переменную. Здесь --_bg - это <any> (потому что на данный момент это не типизированная переменная), с вложенной переменной --hue:

.card {
  --_bg: oklch(98% .01 var(--hue));
  background: var(--_bg);

  @media (prefers-color-scheme: dark) {
    --_bg: oklch(15% .1 var(--hue));
  }
}

Вы можете углубиться на много уровней, но не бесконечно. И можно типизировать некоторые или все переменные. Далее мы создадим несколько типизированных переменных с двухуровневой глубиной.

Актуальность систем проектирования

Давайте сделаем типизированный стартер адаптивной цветовой схемы для светлых и темных тем!

Во-первых, безопасный для типа значение hue. Я сделаю элемент <input type=text>, который будет записывать в это значение все, что мы в него введем. Поскольку он безопасен для типов, мы увидим, как другие пользовательские свойства, зависящие от него, не сломаются, если значение параметра --hue будет установлено в "poots" или что-то в этом роде.

@property --hue {
  syntax: '<angle>';
  initial-value: 5rad;
  inherits: true;
}

Для краткости я буду задавать только поверхностные слои адаптивной цветовой схемы, но это даст понимание процесса создания системы дизайна.

Вот 3 слоя, один из которых будет фоном страницы --surface, и два других, которые будут либо поверх bg страницы, либо под ним. Их начальное значение не вызывает восторга, но мы дойдем до этого в следующей части.

@property --surface {
  syntax: '<color>';
  initial-value: #333;
  inherits: true;
}

@property --surface-over {
  syntax: '<color>';
  initial-value: #444;
  inherits: true;
}

@property --surface-under {
  syntax: '<color>';
  initial-value: #222;
  inherits: true;
}

Важным здесь является то, что они относятся к цветовому типу.

Теперь мы можем присвоить цветам слоёв более понятные значения. При желании можно использовать @media (prefers-color-scheme), но здесь, поскольку я хотел показать светлое и темное с помощью переключателя, я использую :has():

@layer demo.theme {
  html:has(#light:checked) {
    color-scheme: light;
    --surface: oklch(90% .05 var(--hue));
    --surface-over: oklch(99% .02 var(--hue));
    --surface-under: oklch(85% .075 var(--hue));
  }
  
  html:has(#dark:checked) {
    color-scheme: dark;
    --surface: oklch(20% .1 var(--hue));
    --surface-over: oklch(30% .1 var(--hue));
    --surface-under: oklch(15% .1 var(--hue));
  }
}

Вот, собственно, и вся настройка и оркестровка типизированных переменных. Остается только использовать их. Загляните в Codepen, чтобы увидеть все возможные способы их использования для создания адаптивной цветовой схемы: тени, фона и многое другое!

CodePen

Последняя часть

Попробуйте вводить всякую ерунду, в текстовое поле "Theme tint" в CodePen демо-версии. Ни одна из цветовых систем не даст сбоя из-за опечатки или присвоенного значения, не соответствующего типу. Браузер точно знает, как сделать обратный ход и обработать ошибки.

На @property можно построить очень надежную и большую систему. Те же типы безопасности типов при разработке, что и в Typescript, но типы действительно передаются браузеру и соблюдаются. Rad.

Firefox уже почти закончил свою реализацию, что сделает @property кроссбраузерно стабильной ????.

Информацию о поддержке можно узнать на сайте caniuse.

Системы проектирования скоро станут намного умнее и стабильнее.

Комментарии (2)


  1. SUNsung
    09.09.2023 20:21

    Как по мне сначала нужно прийти к кроссбраузерности, а потом уже развивать фичи.

    Ну и что, что на css можно сделать классные градиенты? Один хрен яблофоны это или не поддерживают, или поддерживают через жопу. Из за чего большая часть современных "красивостей" реализуются на js плюс базовый css.


  1. Karlen-ll
    09.09.2023 20:21

    `@property` — не для строгой типизации. Это вспомогательный инструмент для работы с пользовательским вводом. Его корректно использовать только в тех случаях, где разработчик не контролирует в полной мере результирующие стили...

    Например, это может быть полезно разработчику CRM кнопки, которую пользователь встраивает и стилизует у себя на сайте.

    Для строгой типизации — это плохой инструмент, потому, что он увеличит css код.