Стилизация Discourse с помощью переменных: аргумент в пользу более простой семантики

Продолжение обсуждения из Стилизация Discourse с помощью переменных: Покажи и расскажи:

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

Я сам экспериментировал с этим подходом в шаблоне темы Canvas, который по сути представляет собой набор настраиваемых переменных для создания базовых тем:

:root {
  /* Макет */
  --d-max-width: 1110px;
  --canvas-nav-space: 0.75rem;
  --canvas-content-padding: 1.5rem;
  --canvas-topic-list-padding: 0.8em;
  
  /* Базовые стили */
  --canvas-background: var(--secondary);
  --canvas-surface: var(--secondary);
  --canvas-border: 1px solid var(--primary-500);
  --canvas-border-light: 1px solid var(--primary-200);
  
  /* Радиус скругления границ */
  --d-border-radius: 2px;
  --d-border-radius-large: 2px;
  --d-button-border-radius: 2px;
  --d-input-border-radius: var(--d-button-border-radius);
  --d-nav-pill-border-radius: var(--d-button-border-radius);
  
  /* Стили кнопок */
  --canvas-button-padding: 0.5em 0.65em;
  --canvas-button-primary-padding: 0.5em 0.65em;
  
  /* Заголовок */
  --canvas-header-height: 4rem;
  --canvas-header-background: var(--header_background);
  --canvas-header-border: none;
  --canvas-header-shadow: var(--shadow-header);
  
  /* Боковая панель */
  --d-sidebar-width: 17em;
  --d-sidebar-background: var(--secondary);
  --canvas-sidebar-border: 1px solid var(--primary-low);
  --canvas-sidebar-scrollbar: var(--scrollbarWidth);
  --d-sidebar-row-height: 2.2em;
  --d-sidebar-highlight-background: var(--primary-low);

  /* И ещё несколько... */
}

Хотя это неплохо работает для простых корректировок, при попытке масштабировать этот подход я столкнулся с рядом ограничений:

Когнитивная нагрузка и доступность

Обширный список переменных фактически требует использования таблицы соответствий. Это кажется оторванным от того, как обычно работают в компонентных фреймворках, где я ожидаю стилизовать именно компоненты. Возможно, это только моё мнение, но мне кажется, что такая модель мышления смещается с «Я хочу стилизовать этот компонент» на «Мне нужно найти правильное имя переменной».

Отсутствие каскадной логики

Текущая реализация присваивает переменным жёстко заданные значения без установления правильной иерархии каскада:

--d-sidebar-link-color: var(--primary-high);
--d-nav-background-color--active: transparent;
--table-border-width: 1px;

Это означает, что нет наследования от более общих переменных, таких как --link-color или --border-width. Если я хочу внести системные изменения, мне приходится обновлять множество конкретных переменных вместо того, чтобы изменить одно базовое значение.

Разрыв между дизайном и разработкой

Думаю, такой подход создаёт трение при работе между инструментами дизайна (например, Figma) и реализацией. Системы дизайна обычно используют семантические переменные, которые не имеют прямого соответствия этим очень специфичным для реализации переменным.

Альтернативный подход, использующий компонентную архитектуру

Я считаю, что ту же цель можно достичь более естественно, сочетая базовые семантические переменные с надёжным выбором компонентов. Вместо длинного списка конкретных переменных, что если мы могли бы полагаться на уникальные и последовательно именованные классы компонентов по всему Discourse? Например: .d-sidebar, .d-topic-list, .d-header.

А затем дополнить это небольшим набором базовых переменных, которые действительно работают с каскадом так, как задумано в CSS:

/* Установка основы дизайна */
:root {
  --d-border-width: 2px;
  --d-surface-color: #3498db;
  --d-space-1: 1rem;
}

/* Переопределение на уровне компонента при необходимости */
.d-topic-list,
.d-sidebar {
  --d-border-width: 1px;
}

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

Ну, я думаю, что естественная работа CSS — это просто пропустить переменные и сделать так:

/* Задание основы дизайна */
body {
  border-width: 2px;
  background-color: #3498db;
  margin: 1rem;
}

/* Переопределение на уровне компонента при необходимости */
.d-topic-list,
.d-sidebar {
  border-width: 1px;
}

Возможно, я просто старею.

Настоящая проблема вот в чём:

Пример: простое нацеливание на .btn или button:

.btn {
    border: 1px solid red;
}

не затрагивает кнопки постов, но нацеливается на ссылку «просмотры» и гамбургер-меню.

Есть веские причины использовать переменные, возможно, не будем вдаваться в них здесь. Однако это хороший аргумент в пользу того, чтобы не спорить о природе CSS. Мне следовало сформулировать это лучше: дело не в природе CSS, а в лучших практиках стилизации для фреймворка, основанного на компонентах. И я полностью согласен, что кнопки — ещё один отличный пример того, как эти правила не могут быть корректно применены.

Если посмотреть на картину в целом, то было предпринято согласованное усилие по модернизации JavaScript-стороны фронтенд-фреймворка. И я считаю, что это был оглушительный успех. Работа с чистыми стандартами и хорошо структурированными классами теперь действительно приятна. Для меня как для дизайнера это также открыло возможности для создания новых фронтенд-компонентов проще и эффективнее.

Однако у меня не уходит ощущение, что нет аналогичной приверженности приведению дизайн-системы к тем же стандартам. Хотя добавление CSS-переменных для каждого аспекта, безусловно, производительнее и чище текущего подхода, это всё ещё кажется способом избежать более глубоких архитектурных проблем: кодовой базы, полной излишне специфичных объявлений и отсутствия чётких стилей, ограниченных областью компонента. Это ощущается как «более простое» решение, которое обходит более сложную проблему: полную синхронизацию архитектуры стилизации с модульным дизайном фреймворка.

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

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

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

Например, для такого параметра, как --space, его изменение повлияет на все отступы в приложении. Также можно настроить его так, чтобы он влиял только на отступы в списке тем или боковой панели, используя подходы, аналогичные тем, что вы описали.

Это верно для некоторых элементов, но не для других. Буду рад увидеть любые другие примеры, которые вы могли бы привести!

Это действительно проблема. Один из подходов, который мы рассматриваем (пока экспериментально), — это редактор, похожий на то, что делает shadcn здесь:

Хотя это не идеальный подход, мне кажется, что он приблизит нас к тому, чтобы сделать инструмент более доступным для людей, которые не умеют пользоваться инспектором, не имеют доступа к мета-данным для документации или не работают с CSS.

Что касается более компонентного подхода, то это то, к чему мы в конечном итоге стремимся, но Discourse в текущем виде не был разработан с учетом компонентного дизайна, и ожидание этого этапа перед добавлением удобных переменных не рассматривалось.

Добавление некоторых классов для упрощения реализации в определенных разделах звучит как хорошее направление для улучшения удобства использования.


Я согласен с вами :+1:

Короткий ответ: не делайте этого :sweat_smile:

Гораздо лучше указывать класс вторичной кнопки, например btn-default, btn-primary, btn-flat — эти вторичные классы обозначают визуальный тип кнопки.

.btn и button означают скорее «это кнопка» в общем смысле, а не какой-то конкретный внешний вид.

Именно так всё и есть. У нас сейчас нет ресурсов, чтобы начать многолетнюю переписку нашего HTML и CSS, а также всех поддерживаемых тем, особенно учитывая, что мы ещё не завершили многолетний путь обновлений Ember.