Дизайн-система

Один набор решений на два сайта. Шкалы отступов, радиусов, текста и движения общие, тему подставляет сайт: prokhor.me светлую с электрическим фиолетовым, fleypo.com тёмную стеклянную. Ниже каждый элемент показан сразу в обеих темах, и это один и тот же класс, а не две копии вёрстки.

Цвет

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

prokhor.me, бумага
bgфон страницы
surfaceповерхность блока
surface-2утопленная подложка
textосновной текст
text-2второй по важности
mutedподписи
accentакцент
accent-hiсветлый акцент
accent-deepтёмный акцент
signalсигнальный
fleypo.com, ночь
bgфон страницы
surfaceповерхность блока
surface-2утопленная подложка
textосновной текст
text-2второй по важности
mutedподписи
accentакцент
accent-hiсветлый акцент
accent-deepтёмный акцент
signalсигнальный

Состояния

Одинаковы на обоих сайтах: успех, внимание, ошибка и справка не бывают брендовыми.

prokhor.me, бумага
okготово
warnвнимание
dangerошибка
infoсправка
fleypo.com, ночь
okготово
warnвнимание
dangerошибка
infoсправка

Каркас

Ширина содержимого и поля страницы заданы раз: .ds-container держит витрину до 1200, .ds-container--text сужает до 720, чтобы строка была 60-75 знаков. Вертикальный ритм секций даёт .ds-section. Поверхность без внутренних отступов, это .ds-surface, а .ds-rule разделяет, не обводя.

prokhor.me, бумага
ds-surface + ds-container--text

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


под чертой начинается следующая секция
fleypo.com, ночь
ds-surface + ds-container--text

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


под чертой начинается следующая секция

Текст

Модульная шкала от 16. Ступень 13 оставлена намеренно: на ней стоят плотные таблицы медиахаба, где 14 ломает ширину колонок.

prokhor.me, бумага
displayСъешь ещё этих мягких булок
h1Съешь ещё этих мягких булок
h2Съешь ещё этих мягких булок
h3Съешь ещё этих мягких булок
leadСъешь ещё этих мягких булок
bodyСъешь ещё этих мягких булок
smallСъешь ещё этих мягких булок
denseСъешь ещё этих мягких булок
captionСъешь ещё этих мягких булок
fleypo.com, ночь
displayСъешь ещё этих мягких булок
h1Съешь ещё этих мягких булок
h2Съешь ещё этих мягких булок
h3Съешь ещё этих мягких булок
leadСъешь ещё этих мягких булок
bodyСъешь ещё этих мягких булок
smallСъешь ещё этих мягких булок
denseСъешь ещё этих мягких булок
captionСъешь ещё этих мягких булок

Отступы

Сетка 8, мелкий шаг 4. Новое расстояние берём из шкалы, а не из глазомера.

prokhor.me, бумага
sp-1
sp-2
sp-3
sp-4
sp-5
sp-6
sp-8
sp-10
sp-12
sp-16
fleypo.com, ночь
sp-1
sp-2
sp-3
sp-4
sp-5
sp-6
sp-8
sp-10
sp-12
sp-16

Шкала раскладки

Крупные расстояния страницы, каждое вдвое больше предыдущего: промежуток между плитками вдвое меньше поля снаружи. Уже 640 точек вся шкала вдвое меньше.

prokhor.me, бумага
layout-gap
между плитками, поле малой плитки
layout-pad
поле страницы и большой плитки, заголовок → содержимое
layout-sub
между частями полосы
layout-band
между полосами
fleypo.com, ночь
layout-gap
между плитками, поле малой плитки
layout-pad
поле страницы и большой плитки, заголовок → содержимое
layout-sub
между частями полосы
layout-band
между полосами

Радиусы

Семь ступеней. Мелкие метки 4, поля 8, кнопки 12, карточки 16, панели 24 и 32, пилюли круглые.

prokhor.me, бумага
r-xs
r-sm
r-md
r-lg
r-xl
r-2xl
r-pill
fleypo.com, ночь
r-xs
r-sm
r-md
r-lg
r-xl
r-2xl
r-pill

Высота

Контуров вокруг блоков не рисуем, глубину держит тень. Четыре ступени: лежит на странице, приподнят, парит, оторван. Тень всегда нейтральная: цветных теней в системе нет, они выглядят старомодно, и при наведении блок должен подниматься, а не подсвечиваться.

prokhor.me, бумага
shadow-1, лежит
shadow-2, приподнят
shadow-3, парит
shadow-4, оторван
fleypo.com, ночь
shadow-1, лежит
shadow-2, приподнят
shadow-3, парит
shadow-4, оторван

Кнопки

Три высоты (36, 44, 52) и четыре роли. Главная кнопка на экране одна, остальные тише. На тач-устройствах любая дорастает до 44 пикселей.

prokhor.me, бумага
fleypo.com, ночь

Действие. Нажал, что-то произошло: сохранилось, отправилось, открылось.

Когда
  • Действие с последствием: сохранить, отправить, удалить, войти.
  • Главная на экране одна, остальные обычные или тихие.
  • Опасная только для необратимого, и обычно за подтверждением.
Когда нет
  • Переключение вида или отбора: это пилюля, у неё есть состояние «выбрана».
  • Переход на другую страницу: это ссылка, у неё есть адрес.
  • Крупная ступень внутри интерфейса: она для призыва на лендинге.
size: sm | lg, tone: plain | primary | ghost | danger, icon, block

Поля

Подложка вместо рамки, фокус показывает кольцо акцента, ошибка красное кольцо.

prokhor.me, бумага
fleypo.com, ночь

Ввод текста в одну строку. Многострочный ввод, это отдельный элемент, он растёт от содержимого.

Когда
  • Короткий текст: имя, поиск, пароль, число, дата.
  • Рядом с кнопкой: высоты одинаковые по шкале.
Когда нет
  • Выбор из известных вариантов: это список.
  • Да или нет: это чекбокс.
  • Длинный текст: многострочное поле.

Родные элементы формы

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

prokhor.me, бумага
fleypo.com, ночь

Список, флажок, переключатель и ползунок браузера, одетые в тему сайта. Оформлены по самому элементу, а не по классу: новый список одет с рождения.

Когда
  • Выбор одного из нескольких: родной список умеет клавиатуру и поиск по первой букве.
  • Да или нет прямо в строке настроек: флажок.
  • Число в пределах, где точность не важна: ползунок.
  • Произвольный цвет: квадрат выбора, за ним палитра системы с пипеткой.
Когда нет
  • Нужен поиск внутри списка или своя разметка строки: это «Выбор из списка».
  • Да или нет как главное действие экрана: это кнопка, а не флажок.

Значки

Весь набор целиком: и взятые из библиотеки, и нарисованные нами. Прежде чем добавлять новый, посмотрите сюда: рядом уже жили Home и House, Sparkle и Sparkles. Нажатие копирует имя. Подключаются они из app/_ds/icons, напрямую из библиотеки проверка тянуть не даёт.

199 значков

Оценка

Один элемент на три места: строку списка, плитку каталога и окно оценки. Раньше это были три разные вёрстки с тремя высотами и тремя скруглениями, хотя смысл у них один. Цвет от оценки, три ступени: глаз должен видеть разницу до того, как прочтёт цифру.

prokhor.me, бумага
fleypo.com, ночь

От единицы до десятки, десять кнопок. Цвет от оценки тремя ступенями: стоит ли, ответов три.

Когда
  • Своя оценка вещи: фильм, книга, игра.
  • Мелкая в строке, крупная в окне, полоской под обложкой.
Когда нет
  • Ползунок или звёзды: ползунком не попасть в цифру, звёзды всегда «из пяти».
size: sm | lg | bar, value, onPick, label

Крутилка

prokhor.me, бумага
fleypo.com, ночь

Кольцо с дугой акцента: здесь грузится то, что встанет на это место.

Когда
  • Экран корпуса, пока открывается приложение или сайт.
  • Плитка, чьё содержимое приходит позже страницы.
Когда нет
  • Вся страница: у неё свой скелет в loading.tsx.
  • Кнопка после нажатия: у неё свой признак занятости.
size: md | lg, label

Пилюли-переключатели

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

prokhor.me, бумага
fleypo.com, ночь

Состояние, а не действие. Отбор, сортировка, вкладка: что-то одно из ряда сейчас выбрано.

Когда
  • Отбор списка, сортировка, вкладки внутри раздела.
  • Ряд из двух и больше, где видно, что выбрано.
Когда нет
  • Одно действие без состояния: это кнопка.
  • Больше семи вариантов: это список.
  • Подпись без нажатия: это метка.
on: boolean

Выбор из списка

Замена нативному select. Его закрытую часть оформить можно, а открытый список рисует операционная система, и до него не дотягивается ни один класс. Поэтому список рисуем сами: вид тот же, что у остальных полей, а закрытие по щелчку мимо и по Escape дописано руками, потому что нативный элемент давал их даром.

prokhor.me, бумага
fleypo.com, ночь

Один вариант из известного набора: свой список с поиском и своей разметкой строки.

Когда
  • От четырёх вариантов, которые помещаются на экран целиком.
  • Значение показывается закрытым, список открывается по нажатию.
Когда нет
  • До трёх вариантов: пилюли в ряд, видно всё сразу.
  • Простой выбор без поиска: родной список, он одет и работает клавиатурой.
value, options, onChange, label

Чекбокс и радио

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

prokhor.me, бумага
СделаноПовторятьНедоступно
На деньНа неделюНа месяц
fleypo.com, ночь
СделаноПовторятьНедоступно
На деньНа неделюНа месяц

Чекбокс, это «да или нет», независимо от соседей. Радио, это «один из нескольких», где выбор одного снимает остальные.

Когда
  • Чекбокс: сделано или нет, включено или нет, отметить несколько строк.
  • Радио: от двух до пяти взаимоисключающих вариантов, все видны сразу.
Когда нет
  • Немедленное действие по нажатию: это кнопка, у неё нет состояния.
  • Радио на два варианта «вкл/выкл»: это чекбокс.
  • Больше пяти радио: это список.
checked, onChange, label

Аватар

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

prokhor.me, бумага
ПИПИПИПрохор
fleypo.com, ночь
ПИПИПИПрохор

Кто. Снимок или инициалы в кружке, размер из шкалы управления, чтобы стоять в одном ряду с кнопками.

Когда
  • Человек в строке, в шапке, в подписи к записи.
  • Нет снимка: инициалы на подложке, а не пустой кружок.
Когда нет
  • Знак бренда или сервиса: это марка, у неё своя форма.
  • Картинка содержания: это медиа.
size: sm | md | lg, src, name

Пилюли и метки

Пилюля несёт слово целиком, метка мелкий капслок, точка показывает состояние.

prokhor.me, бумага
На связиСвободен с сентябряновое В работе Просрочено
fleypo.com, ночь
На связиСвободен с сентябряновое В работе Просрочено

Островок

Форма одна на три состояния: точка, капсула, панель. Место постоянное, силуэт непрерывный, значок переживает переход, и по нему видно, что предмет тот же.

prokhor.me, бумага
fleypo.com, ночь

Живая активность на своём постоянном месте: форма растёт под то, что показывает, и ужимается обратно.

Когда
  • Дело идёт само и человек за ним следит: запись, выгрузка, таймер, сборка.
  • За слежением иногда нужно действие: пауза, отмена, повтор.
  • Место на экране одно и занято всё время, пока дело не кончилось.
Когда нет
  • Сообщение о том, что уже случилось: это уведомление, оно приходит и уходит.
  • Список, который не влезает в две строки: это панель, у неё своя прокрутка.
  • Подсказка у элемента: это всплывающая подпись, она живёт у своего места, а не в углу.
  • Одно действие без состояния: это кнопка.
state: dot | compact | full, tone: ink | glass, press, icon, value, label

Ростер

Нумерованный перечень однородных вещей: кружок с номером, приглушённая подпись, колонки по строкам. Перечень, а не дерево: у пунктов нет вложенности и состояний. «А также» даёт ту же нумерацию в выноске для второстепенного.

prokhor.me, бумага
  1. Веб-сайт
  2. Приложение
  3. UI/UX
  4. Айдентика
  5. Логотип
  6. Типографика
А также
  1. Продуктовый дизайн
  2. Конференции и мероприятия
  3. Питч-деки
  4. Стенды и выставки
  5. Презентационные материалы
fleypo.com, ночь
  1. Веб-сайт
  2. Приложение
  3. UI/UX
  4. Айдентика
  5. Логотип
  6. Типографика
А также
  1. Продуктовый дизайн
  2. Конференции и мероприятия
  3. Питч-деки
  4. Стенды и выставки
  5. Презентационные материалы

Нумерованный перечень однородных вещей: кружок с номером, приглушённая подпись, несколько колонок по строкам.

Когда
  • Список одного уровня, где важны состав и счёт: направления, услуги, охваты.
  • Пункт-ссылка ведёт в раздел: перечень работает навигацией.
  • «А также» выноской (RosterNote): второстепенное рядом с основным, крупный заголовок слева.
Когда нет
  • У пунктов вложенность или состояние: это дерево (SkillTree), а не перечень.
  • Набор значков без счёта: это ряд (ToolRow).
  • Шаги, которые делают по порядку: номер там значит ход, а не просто индекс.
columns: 1 | 2 | 3, items: { label, href? }[]

Поверхности

Карточка держится тенью, не обводкой. Стеклянная размывает то, что под ней, и живёт поверх картинок и фона.

prokhor.me, бумага
обычная
Карточка

Лежит на странице, тень еле заметна.

приподнятая
Важнее соседей
стеклянная
Поверх фона
fleypo.com, ночь
обычная
Карточка

Лежит на странице, тень еле заметна.

приподнятая
Важнее соседей
стеклянная
Поверх фона

Поверхность для группы связанного: тень или подложка, без рамки.

Когда
  • Единица списка со своими действиями.
  • Островок другой темы внутри страницы: стекло, тёмное окно.
Когда нет
  • Один абзац текста: карточка вокруг него, это шум.
  • Карточка в карточке: вложенность держат отступы.
lift: flat | rest | raised

Окно подтверждения

Спрашивает перед необратимым: удалить, стереть, выйти без сохранения. Одно на оба сайта, кнопки внутри обычные кнопки системы, опасное действие справа.

prokhor.me, бумага

Удалить материал без возможности вернуть?

fleypo.com, ночь

Удалить материал без возможности вернуть?

Сайт обновился

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

prokhor.me, бумага
Сайт обновилсяНа сервере уже новая сборка: 8f21c0a
fleypo.com, ночь
Сайт обновилсяНа сервере уже новая сборка: 8f21c0a

Режим админа

Служебный слой всегда тёмный, на обоих сайтах одинаково: он не спорит с темой страницы, а лежит поверх неё. Пунктир вокруг текста, единственная намеренная обводка в системе: она показывает режим, а не оформляет блок.

prokhor.me, бумага
Ключ администратора
Ключ не подошёл
Правки сохранены

Так выглядит текст, который админ может править прямо на странице.

fleypo.com, ночь
Ключ администратора
Ключ не подошёл
Правки сохранены

Так выглядит текст, который админ может править прямо на странице.

Как пользоваться. Значения берём токенами: var(--sp-5), var(--r-lg), var(--ds-text). Готовый цвет с решёткой, произвольный радиус и размер шрифта вне шкалы ловит scripts/ds-lint.mjs на предкоммитном хуке. Новый общий элемент кладём в app/_ds/ds.css и обязательно показываем здесь, иначе система расходится с сайтами. Полные правила: docs/UI_RULES.md.