Хабр, привет! Я снова пришёл к вам со статьёй, где показываю мои любимые техники вёрстки. Моя цель — поделиться опытом с вами. Я использую не только трюки известных экспертов, есть лично мои придумки. Но, пожалуйста, относитесь к этому контенту, как просто альтернативному мнению. Мои техники не являются единственными правильными решениями.


Сегодня я расскажу:

  • какие правила у меня есть при именовании CSS-переменных для дизайн-систем;
  • на какие логические свойства вам стоит обратить внимание;
  • зачем нужно делать прозрачные рамки;
  • как я делаю зависимость значения одного свойства от других более явным;
  • про пользу «внутренних» переменных.

Давайте посмотрим, что я вам подготовил.


▍ Мои названия переменных для дизайн-систем


Есть шутка, что одна из главных сложностей в программировании — придумать название переменным. Блин, теперь так же можно сказать про CSS. Я без шуток по часу думал, как лучше назвать переменные. В ходе своих мук выявил для себя несколько правил.


Начнём с названия переменных для цвета. Я выделяю несколько групп: основной цвет, цвет акцента и вспомогательный цвет. Для каждой переменной есть базовый, более светлый и тёмный оттенки.


:root {
  /* основной цвет */
  --main-color-light: #fed330;
  --main-color: #fed330;
  --main-color-dark: #fbc604;

  /* акцентирующий цвет */
  --accent-color-light: #c6b2ff;
  --accent-color: #6d47d9;
  --accent-color-dark: #5130b3;

 /* вспомогательный цвет */
  --assistive-color-light: #fafafa;
  --assistive-color: #eee;
  --assistive-color-dark: #b8b4b4;
}

Постфиксы -light и -dark также отлично подходят для свойств, которые могут быть тоньше и толще. Например, начертание шрифта.


:root {
  --font-weight-light: 300;
  --font-weight: 400;
  --font-weight-dark: 700;
} 

В любой дизайн-системе есть белый и тёмный цвет. В светлой теме интерфейса первый цвет главный, а второй — второстепенный. В тёмной всё наоборот. Для их обозначения я использую две переменных --main-mode-color и --accent-mode-color.


:root {
  /* цвет темы интерфейса */
  --main-mode-color: #fcfcfc;
  --accent-mode-color: #222;
}

@media (prefers-color-scheme: dark) {

  :root {
    --main-mode-color: #1e2229;
    --accent-mode-color: #ebecef;
  }	
} 

Ещё в дизайн-системах важно задавать размеры. Я думал-думал, как же назвать переменные, чтобы всей команде было понятно. А потом вспомнил про размерную сетку одежды. Все же знают, что размер s меньше, чем 2xl. Мне показалось это тем, что нужно!


К этой категории переменных я отношу размер шрифта, скругление, пространство, включая размеры элементов и отступов.


:root {
  /* размер пространства (отступы, размеры элементов) */
  --space-2xs: 0.25rem;
  --space-xs: 0.5rem;
  --space-s: 0.75rem;
  --space-m: 1rem;
  --space-l: 1.25rem;
  --space-xl: 1.5rem;
  --space-2xl: 1.75rem;	

  /* размер текста */
  --font-size-xs: 0.5rem;
  --font-size-s: 0.75rem;
  --font-size-m: 1rem;
  --font-size-l: 1.25rem;
  --font-size-xl: 1.5rem;
  --font-size-2xl: 1.75rem;
}

Мне нравится этот способ ещё и тем, что в любое время очень легко добавить новое значение. Достаточно использовать предыдущий или следующий размер. Например, -2xs или -3xl.


Мне теперь интересно, каким правилам следуете вы. Поделитесь, пожалуйста, в комментариях. Я с удовольствием прочитаю.


▍ Какие логические свойства должны вызвать у вас интерес


В прошлом выпуске я показал, как я использую свойство inset. Но я не сказал, что оно относится к виду свойств под названием логические. Без глубоких деталей, данный вид может менять своё отображение при переключении направления текста.


Я в принципе фанат идеи, что интерфейс должен быть адаптирован под любое направление текста. Неважно справа-налево или японский стиль, моя вёрстка должна адаптироваться. Рассмотрим, как я это делаю


Сперва напишу привычный для вас код.


.page__cv-skill-number {
  display: grid;
  place-items: center;

  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background-color: #eee;

  position: absolute;
  top: 0;
  left: 0;
}

Моя версия кода будет без свойств width, height, top, и left. Вместо них будут свойства inline-size, block-size, inset-block-start и inset-inline-start.


.page__cv-skill-number {
  display: grid;
  place-items: center;

  inline-size: 2rem;
  block-size: 2rem;
  border-radius: 50%;
  background-color: #eee;

  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
}

Конечно, нужно время, чтобы привыкнуть к логическим свойствам. Да, откровенно говоря, в большинстве случаев адаптация под разные языки — это редкая задача. Просто мне нравится так делать. Но вам я хочу предложить использовать часть логических свойств!


Начну сразу с примера. Всегда есть контейнер, который отвечает за ширину контента. Вы его центрируете и задаёте внутренние отступы.


/* вариант №1 */
.container {
  max-width: 75rem;
  padding-left: 1rem;
  padding-right: 1rem;
  margin-left: auto;
  margin-right: auro; 
}

/* или вариант №2 */
.container {
  max-width: 75rem;
  padding: 0 1rem;
  margin: 0 auto; 
}

Привычными свойствами мы вынуждены либо писать больше кода, либо устанавливать 0 в качестве значения. Лично я был фанатом первого варианта. Писать 0, чтобы задать отступы по вертикали, которых нет, было для меня слишком странным.


Теперь я использую свойства padding-inline, padding-block, margin-inline и margin-block. С помощью них мы зададим значения только там, где требуется, без раздутия кода.


.container {
  max-width: 75rem;
  padding-inline: 1rem;
  margin-inline: auto;
}

Лаконично и логично! Эти свойства я использую всегда, когда требуется установить значения только по одной оси. Надеюсь, и вам они помогут.


▍ Мои любимые прозрачные рамки


Цифровая доступность очень близка для меня. Конечно, я не мог оставить вас без лайфхаков в этой теме. Один из них касается режима высокой контрастности в Windows.


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



Блок с призывом подписаться отлично выделен на белом фоне страницы. В режиме высокой контрастности Windows так не будет. Блок сольётся с основным цветом страницы.



И как же быть? Без границ как-то сложно ориентироваться. Нам поможет значение transparent. Мы его установим для свойства border. Пусть дизайнер не сделал границы. Нам же он не запрещал их делать!


.widget {
  border: 1px solid transparent;
  /* оставшиеся стили */
}



В обычном режиме мы не увидим прозрачные рамки. А в режиме высокой контрастности они станут сплошными! Всё благодаря значению transparent. В режиме высокой контрастности оно становится видимым. Мне кажется, это гениальный лайфхак. Его я узнал от Sarah Higley.


▍ Делаем зависимость одного значения свойства от другого явной


При рефакторинге вёрстки есть очень сложная проблема. Нужно учесть все значения, которые зависят от другого. Если этого не сделать, то здравствуйте несколько часов поиска причины, почему всё поехало.


Рассмотрим, что я имею в виду. В качестве примера буду использовать раздел с описанием первого навыка из моего недавнего проекта.



Разметка блока выглядит так:


<body class="page">
  <!-- предыдущая часть страницы -->

  <section class="page__cv-skill-box" aria-labelledby="skill-heading-5">
    <span class="page__cv-skill-number" aria-hidden="true">1</span>
    <h3 id="skill-heading-5" class="uia-heading ra-heading">Developing player experience</h3>
    <div class="page__cv-skill-description">
      <p>Working with game designers to create interesting features.</p>
    </div>
  </section>

  <!-- оставшаяся часть страницы -->
</body>

Обратите внимание на элемент с номером навыка. Для его позиционирования я выбрал способ на основе свойства position и значения absolute. По этой причине мне нужно было установить padding, чтобы освободить место под элемент. Дело оставалось за малым — посчитать значение.


Оно состояло из ширины элемента .page__cv-skill-number (2.5rem) и отступа между ним и контентом. Те же 2.5rem. В итоге вышло 5rem.


.page__cv-skill-box {
  position: relative;
  padding-left: 5rem;
}

.page__cv-skill-number {
  width: 2.5rem;
  height: 2.5rem; 

  position: absolute;
  top: 0;
  left: 0;

  /* оставшиеся CSS */
}

Всё работало. Только у меня возник вопрос к себе: «Что будет, если потребуется изменить размер элемента с цифрой?». Мне придётся править код в трёх местах. А самое страшное — помнить о них. А код в этой проблеме мне никак не помогает.


Что нужно сделать? Использовать пользовательские свойства. Задать с помощью них размеры элемента и отступ между ним и контентом. Так у меня появились переменные --page-skill-number-size и --page-skill-number-gap.


.page__cv-skill-box {
  --page-skill-number-size: 2.5rem;
  --page-skill-number-gap: 2.5rem;

  position: relative;
  padding-left: calc(var(--page-skill-number-size) + var(--page-skill-number-gap));
}

.page__cv-skill-number {
  width: var(--page-skill-number-size);
  height: var(--page-skill-number-size);

  position: absolute;
  top: 0;
  left: 0;

  /* оставшиеся CSS */
}

По моему мнению, так код выглядит понятнее. В нём явно видна зависимость одного значения от других. Конечно, вы можете сказать, что лучше без зависимостей. Но, к сожалению, в вёрстке так не бывает. Они всегда были, есть и будут.


Правда в этом коде всё же есть проблема. О ней в следующем разделе.


▍ Где полезны «внутренние» переменные


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


.page__cv-skill-box {
  --page-skill-number-size: 2.5rem;
  --page-skill-number-gap: 2.5rem;

  position: relative;
  padding-left: calc(var(--page-skill-number-size) + var(--page-skill-number-gap));
}

.page__cv-skill-number {
  width: var(--page-skill-number-size);
  height: var(--page-skill-number-size);

  position: absolute;
  top: 0;
  left: 0;

  /* оставшиеся CSS */
}

@media (max-width: 767px) {

  .page__cv-skill-box {
    --page-skill-number-size: 2rem;
    --page-skill-number-gap: 1rem;
  }
}

Так всё работает. Только этот код легко сломать, переместив медиа-запрос перед основным кодом. Так значение 2.5rem переопределят значения 2rem и 1rem.


@media (max-width: 767px) {

  .page__cv-skill-box {
    --page-skill-number-size: 2rem;
    --page-skill-number-gap: 1rem;
  }
}

.page__cv-skill-box {
  --page-skill-number-size: 2.5rem;
  --page-skill-number-gap: 2.5rem;

  position: relative;
  padding-left: calc(var(--page-skill-number-size) + var(--page-skill-number-gap));
}

.page__cv-skill-number {
  width: var(--page-skill-number-size);
  height: var(--page-skill-number-size);

  position: absolute;
  top: 0;
  left: 0;

  /* оставшиеся CSS */
}

Что делать? Использовать значения по умолчанию. Только здесь ждёт проблема. У нас переменная --page-skill-number-size используется в трёх местах. Поэтому нужна «внутренняя» переменная для сохранения значения. Если оно передаётся откуда-то, то используется оно. Если нет, то значение по умолчанию.


Также с помощью символа «_» я обозначаю, что переменная «внутренняя».


.page__cv-skill-box {
  --_page-skill-number-size: var(--page-skill-number-size, 2.5rem);

  position: relative;
  padding-left: calc(var(--_page-skill-number-size) + var(--page-skill-number-gap, 2.5rem));
}

.page__cv-skill-number {
  width: var(--_page-skill-number-size);
  height: var(--_page-skill-number-size);

  position: absolute;
  top: 0;
  left: 0;

  /* оставшиеся CSS */
}

@media (max-width: 767px) {

  .page__cv-skill-box {
    --page-skill-number-size: 2rem;
    --page-skill-number-gap: 1rem;
  }
}

Последним, что я изменил, стал порядок передачи значений в переменные. Я сторонник «Mobile First». Поэтому сначала определю значения для смартфонов.


.page__cv-skill-box {
  --_page-skill-number-size: var(--page-skill-number-size, 2rem);

  position: relative;
  padding-left: calc(var(--_page-skill-number-size) + var(--page-skill-number-gap, 1rem));
}

.page__cv-skill-number {
  width: var(--_page-skill-number-size);
  height: var(--_page-skill-number-size);

  position: absolute;
  top: 0;
  left: 0;

  /* оставшиеся CSS */
}

@media (min-width: 768px) {

  .page__cv-skill-box {
    --page-skill-number-size: 2.5rem;
    --page-skill-number-gap: 2.5rem;
  }
}

▍ Заключение


Давайте подведём итог. В этой статье мы рассмотрели:

  • мои правила именования CSS-переменных для дизайн-систем;
  • как избежать использования 0 при объявлении свойств margin и padding;
  • пользу значения transparent вместе со свойством border в режиме высокой контрастности;
  • как сделать зависимость значений друг от друга более явной с помощью пользовательских свойств;
  • «внутренние» свойства, позволяющие передать значения в несколько мест.

Оставляю ссылку на первый выпуск. Тоже посмотрите. Спасибо за чтение!


P.S. Помогаю больше узнать про CSS в своём ТГ-канале CSS isn't magic. Присоединяйтесь. Ссылка в профиле.


Telegram-канал со скидками, розыгрышами призов и новостями IT ?

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


  1. wadowad
    23.07.2024 15:30

    Если xl равен 1.5, то 2xl равно 3. А вот 1.75 - это наверно xxl.


    1. mDoll
      23.07.2024 15:30

      А чем «2xl» от «xxl» отличается?


  1. davidaganov
    23.07.2024 15:30
    +2

    мне нравится способ именования переменных привычный многим из бутстрапа или таилвинда. касаемо цветов чтобы, например, не возникло ситуации когда у нас много серых цветов в проекте и light gray, gray и dark gray уже недостаточно - именовать их как gray-100, gray-200, gray-300... а для размеров это xs, sm, md, lg, xl, 2xl. и т.д. как по мне эти два подхода знакомы куда большему количеству людей и более гибкие, если рассматривать цвета


    1. melnik909 Автор
      23.07.2024 15:30

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


  1. Spaceoddity
    23.07.2024 15:30
    +1

    Начнём с названия переменных для цвета. Я выделяю несколько групп: основной цвет, цвет акцента и вспомогательный цвет. Для каждой переменной есть базовый, более светлый и тёмный оттенки.

    А для чего это? Разве это всё не пляшет от дизайн-макета и количества цветов в нём?

    Постфиксы -light и -dark также отлично подходят для свойств, которые могут быть тоньше и толще. Например, начертание шрифта.

    Да нет. В том же CSS для этого используется light, regular, medium, bold... Вот это отлично подходит)) Вплоть до того, что можно и не заводить отдельную переменную))

    Эти свойства я использую всегда, когда требуется установить значения только по одной оси. Надеюсь, и вам они помогут.

    Ну тут же сразу виден потенциальный баг, если контейнер сменит свойство display (при смене адаптива) или позиционирование...


    1. melnik909 Автор
      23.07.2024 15:30

      А для чего это? Разве это всё не пляшет от дизайн-макета и количества цветов в нём?

      Для систематизации значений из дизайн-системы.

      Ну тут же сразу виден потенциальный баг, если контейнер сменит свойство display (при смене адаптива) или позиционирование...

      Какой баг? Давайте обсудим


  1. bubn0ff
    23.07.2024 15:30

    Начнём с названия переменных для цвета. Я выделяю несколько групп: основной цвет, цвет акцента и вспомогательный цвет.

    А почему бы не именовать переменные цвета по названию цвета? Например, так:

    $font-color--light-slate-grey: #85888F;
    $font-color--tangerine: #EF8711;

    Имена можно брать тут: http://www.htmlcsscolor.com


    1. vlmonk
      23.07.2024 15:30

      $font-color--85888f: #85888F;
      $font-color--ef8711: #EF8711;


    1. melnik909 Автор
      23.07.2024 15:30

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


      1. bubn0ff
        23.07.2024 15:30

        Вы правы. Значит вариант "... основной цвет, цвет акцента и вспомогательный цвет..." тут больше подходит.


  1. St0rY
    23.07.2024 15:30

    Мне тоже временами очень сложно дать название переменной) целое испытание) взял для себя полезности из статьи, спасибо. Подскажи пожалуйста, в вёрстке блока у тебя тег 'p' обернут ещё в 'div', обертка для какой цели?


    1. melnik909 Автор
      23.07.2024 15:30

      Есть несколько причин. Первая, контейнер нужен на случай нескольких абзацев. Вторая, я не добавляю атрибут class для элементов, где есть типографика. Стили вешаю через :not([class]). Получается у меня так:

      <body class="page">
        <section class="page__section">
          <h2 class="page__section-heading">About me</h2>
          <div class="page__section-content">
            <p>I'm a content creator.</p>
            <p>I'm an accessibility designer.</p>
          </div>
        </section>
        </body>
      p:not([class]) {
        margin-block: 1.5rem 0;
      }