Дизайн-система
Один набор решений на два сайта. Шкалы отступов, радиусов, текста и движения общие, тему подставляет сайт: prokhor.me светлую с электрическим фиолетовым, fleypo.com тёмную стеклянную. Ниже каждый элемент показан сразу в обеих темах, и это один и тот же класс, а не две копии вёрстки.
Цвет
Вёрстка не знает про фиолетовый и белый, она знает про фон, поверхность, текст и акцент. Имена одни, значения разные, поэтому один класс одевается по сайту сам.
Состояния
Одинаковы на обоих сайтах: успех, внимание, ошибка и справка не бывают брендовыми.
Каркас
Ширина содержимого и поля страницы заданы раз: .ds-container держит витрину до 1200, .ds-container--text сужает до 720, чтобы строка была 60-75 знаков. Вертикальный ритм секций даёт .ds-section. Поверхность без внутренних отступов, это .ds-surface, а .ds-rule разделяет, не обводя.
Ширина текста ограничена, поэтому строка не расползается на всю ширину экрана и глаз находит начало следующей.
Ширина текста ограничена, поэтому строка не расползается на всю ширину экрана и глаз находит начало следующей.
Текст
Модульная шкала от 16. Ступень 13 оставлена намеренно: на ней стоят плотные таблицы медиахаба, где 14 ломает ширину колонок.
displayСъешь ещё этих мягких булокh1Съешь ещё этих мягких булокh2Съешь ещё этих мягких булокh3Съешь ещё этих мягких булокleadСъешь ещё этих мягких булокbodyСъешь ещё этих мягких булокsmallСъешь ещё этих мягких булокdenseСъешь ещё этих мягких булокcaptionСъешь ещё этих мягких булокdisplayСъешь ещё этих мягких булокh1Съешь ещё этих мягких булокh2Съешь ещё этих мягких булокh3Съешь ещё этих мягких булокleadСъешь ещё этих мягких булокbodyСъешь ещё этих мягких булокsmallСъешь ещё этих мягких булокdenseСъешь ещё этих мягких булокcaptionСъешь ещё этих мягких булокОтступы
Сетка 8, мелкий шаг 4. Новое расстояние берём из шкалы, а не из глазомера.
sp-1sp-2sp-3sp-4sp-5sp-6sp-8sp-10sp-12sp-16sp-1sp-2sp-3sp-4sp-5sp-6sp-8sp-10sp-12sp-16Шкала раскладки
Крупные расстояния страницы, каждое вдвое больше предыдущего: промежуток между плитками вдвое меньше поля снаружи. Уже 640 точек вся шкала вдвое меньше.
layout-gaplayout-padlayout-sublayout-bandlayout-gaplayout-padlayout-sublayout-bandРадиусы
Семь ступеней. Мелкие метки 4, поля 8, кнопки 12, карточки 16, панели 24 и 32, пилюли круглые.
r-xsr-smr-mdr-lgr-xlr-2xlr-pillr-xsr-smr-mdr-lgr-xlr-2xlr-pillВысота
Контуров вокруг блоков не рисуем, глубину держит тень. Четыре ступени: лежит на странице, приподнят, парит, оторван. Тень всегда нейтральная: цветных теней в системе нет, они выглядят старомодно, и при наведении блок должен подниматься, а не подсвечиваться.
Кнопки
Три высоты (36, 44, 52) и четыре роли. Главная кнопка на экране одна, остальные тише. На тач-устройствах любая дорастает до 44 пикселей.
Действие. Нажал, что-то произошло: сохранилось, отправилось, открылось.
- Действие с последствием: сохранить, отправить, удалить, войти.
- Главная на экране одна, остальные обычные или тихие.
- Опасная только для необратимого, и обычно за подтверждением.
- Переключение вида или отбора: это пилюля, у неё есть состояние «выбрана».
- Переход на другую страницу: это ссылка, у неё есть адрес.
- Крупная ступень внутри интерфейса: она для призыва на лендинге.
size: sm | lg, tone: plain | primary | ghost | danger, icon, blockПоля
Подложка вместо рамки, фокус показывает кольцо акцента, ошибка красное кольцо.
Ввод текста в одну строку. Многострочный ввод, это отдельный элемент, он растёт от содержимого.
- Короткий текст: имя, поиск, пароль, число, дата.
- Рядом с кнопкой: высоты одинаковые по шкале.
- Выбор из известных вариантов: это список.
- Да или нет: это чекбокс.
- Длинный текст: многострочное поле.
Родные элементы формы
Список, флажок, переключатель и ползунок браузер рисует средствами системы: на Windows серая рамка со стрелкой и синяя подсветка выбранного. Про тему он не знает, поэтому оформлен по самому элементу, а не по классу на нём.
Список, флажок, переключатель и ползунок браузера, одетые в тему сайта. Оформлены по самому элементу, а не по классу: новый список одет с рождения.
- Выбор одного из нескольких: родной список умеет клавиатуру и поиск по первой букве.
- Да или нет прямо в строке настроек: флажок.
- Число в пределах, где точность не важна: ползунок.
- Произвольный цвет: квадрат выбора, за ним палитра системы с пипеткой.
- Нужен поиск внутри списка или своя разметка строки: это «Выбор из списка».
- Да или нет как главное действие экрана: это кнопка, а не флажок.
Значки
Весь набор целиком: и взятые из библиотеки, и нарисованные нами. Прежде чем добавлять новый, посмотрите сюда: рядом уже жили Home и House, Sparkle и Sparkles. Нажатие копирует имя. Подключаются они из app/_ds/icons, напрямую из библиотеки проверка тянуть не даёт.
Оценка
Один элемент на три места: строку списка, плитку каталога и окно оценки. Раньше это были три разные вёрстки с тремя высотами и тремя скруглениями, хотя смысл у них один. Цвет от оценки, три ступени: глаз должен видеть разницу до того, как прочтёт цифру.
От единицы до десятки, десять кнопок. Цвет от оценки тремя ступенями: стоит ли, ответов три.
- Своя оценка вещи: фильм, книга, игра.
- Мелкая в строке, крупная в окне, полоской под обложкой.
- Ползунок или звёзды: ползунком не попасть в цифру, звёзды всегда «из пяти».
size: sm | lg | bar, value, onPick, labelКрутилка
Кольцо с дугой акцента: здесь грузится то, что встанет на это место.
- Экран корпуса, пока открывается приложение или сайт.
- Плитка, чьё содержимое приходит позже страницы.
- Вся страница: у неё свой скелет в loading.tsx.
- Кнопка после нажатия: у неё свой признак занятости.
size: md | lg, labelПилюли-переключатели
Порядок, отбор, вид. Размера у пилюли нет намеренно: она всегда стоит в ряду с кнопками, значит высота у неё ровно одна и совпадает с кнопкой.
Состояние, а не действие. Отбор, сортировка, вкладка: что-то одно из ряда сейчас выбрано.
- Отбор списка, сортировка, вкладки внутри раздела.
- Ряд из двух и больше, где видно, что выбрано.
- Одно действие без состояния: это кнопка.
- Больше семи вариантов: это список.
- Подпись без нажатия: это метка.
on: booleanВыбор из списка
Замена нативному select. Его закрытую часть оформить можно, а открытый список рисует операционная система, и до него не дотягивается ни один класс. Поэтому список рисуем сами: вид тот же, что у остальных полей, а закрытие по щелчку мимо и по Escape дописано руками, потому что нативный элемент давал их даром.
Один вариант из известного набора: свой список с поиском и своей разметкой строки.
- От четырёх вариантов, которые помещаются на экран целиком.
- Значение показывается закрытым, список открывается по нажатию.
- До трёх вариантов: пилюли в ряд, видно всё сразу.
- Простой выбор без поиска: родной список, он одет и работает клавиатурой.
value, options, onChange, labelЧекбокс и радио
Кнопка с ролью, а не нативный элемент: нативный рисует система, и оформить его можно только дорисовкой поверх. Коробочка из шкалы отступов, область нажатия, нижняя ступень шкалы управления. Отмеченное красится цветом действия: чекбокс отвечает «да», а не «хорошо».
Чекбокс, это «да или нет», независимо от соседей. Радио, это «один из нескольких», где выбор одного снимает остальные.
- Чекбокс: сделано или нет, включено или нет, отметить несколько строк.
- Радио: от двух до пяти взаимоисключающих вариантов, все видны сразу.
- Немедленное действие по нажатию: это кнопка, у неё нет состояния.
- Радио на два варианта «вкл/выкл»: это чекбокс.
- Больше пяти радио: это список.
checked, onChange, labelАватар
Три размера со шкалы управления, чтобы стоять в одном ряду с кнопками. Нет снимка, есть инициалы на подложке цвета действия, а не пустой кружок.
Кто. Снимок или инициалы в кружке, размер из шкалы управления, чтобы стоять в одном ряду с кнопками.
- Человек в строке, в шапке, в подписи к записи.
- Нет снимка: инициалы на подложке, а не пустой кружок.
- Знак бренда или сервиса: это марка, у неё своя форма.
- Картинка содержания: это медиа.
size: sm | md | lg, src, nameПилюли и метки
Пилюля несёт слово целиком, метка мелкий капслок, точка показывает состояние.
Островок
Форма одна на три состояния: точка, капсула, панель. Место постоянное, силуэт непрерывный, значок переживает переход, и по нему видно, что предмет тот же.
Заварка улуна
Медиатека, 7 из 12 файлов
Заварка улуна
Медиатека, 7 из 12 файлов
Живая активность на своём постоянном месте: форма растёт под то, что показывает, и ужимается обратно.
- Дело идёт само и человек за ним следит: запись, выгрузка, таймер, сборка.
- За слежением иногда нужно действие: пауза, отмена, повтор.
- Место на экране одно и занято всё время, пока дело не кончилось.
- Сообщение о том, что уже случилось: это уведомление, оно приходит и уходит.
- Список, который не влезает в две строки: это панель, у неё своя прокрутка.
- Подсказка у элемента: это всплывающая подпись, она живёт у своего места, а не в углу.
- Одно действие без состояния: это кнопка.
state: dot | compact | full, tone: ink | glass, press, icon, value, labelРостер
Нумерованный перечень однородных вещей: кружок с номером, приглушённая подпись, колонки по строкам. Перечень, а не дерево: у пунктов нет вложенности и состояний. «А также» даёт ту же нумерацию в выноске для второстепенного.
- Веб-сайт
- Приложение
- UI/UX
- Айдентика
- Логотип
- Типографика
- Продуктовый дизайн
- Конференции и мероприятия
- Питч-деки
- Стенды и выставки
- Презентационные материалы
- Веб-сайт
- Приложение
- UI/UX
- Айдентика
- Логотип
- Типографика
- Продуктовый дизайн
- Конференции и мероприятия
- Питч-деки
- Стенды и выставки
- Презентационные материалы
Нумерованный перечень однородных вещей: кружок с номером, приглушённая подпись, несколько колонок по строкам.
- Список одного уровня, где важны состав и счёт: направления, услуги, охваты.
- Пункт-ссылка ведёт в раздел: перечень работает навигацией.
- «А также» выноской (RosterNote): второстепенное рядом с основным, крупный заголовок слева.
- У пунктов вложенность или состояние: это дерево (SkillTree), а не перечень.
- Набор значков без счёта: это ряд (ToolRow).
- Шаги, которые делают по порядку: номер там значит ход, а не просто индекс.
columns: 1 | 2 | 3, items: { label, href? }[]Поверхности
Карточка держится тенью, не обводкой. Стеклянная размывает то, что под ней, и живёт поверх картинок и фона.
Лежит на странице, тень еле заметна.
Лежит на странице, тень еле заметна.
Поверхность для группы связанного: тень или подложка, без рамки.
- Единица списка со своими действиями.
- Островок другой темы внутри страницы: стекло, тёмное окно.
- Один абзац текста: карточка вокруг него, это шум.
- Карточка в карточке: вложенность держат отступы.
lift: flat | rest | raisedОкно подтверждения
Спрашивает перед необратимым: удалить, стереть, выйти без сохранения. Одно на оба сайта, кнопки внутри обычные кнопки системы, опасное действие справа.
Удалить материал без возможности вернуть?
Удалить материал без возможности вернуть?
Сайт обновился
Служебная плашка внизу экрана: на сервере уже новая сборка, а в браузере открыта старая. Насильно не перезагружаем, решает человек. Всегда тёмная, как и остальной служебный слой, поэтому в обеих темах выглядит одинаково.
Режим админа
Служебный слой всегда тёмный, на обоих сайтах одинаково: он не спорит с темой страницы, а лежит поверх неё. Пунктир вокруг текста, единственная намеренная обводка в системе: она показывает режим, а не оформляет блок.
Так выглядит текст, который админ может править прямо на странице.
Так выглядит текст, который админ может править прямо на странице.
var(--sp-5), var(--r-lg), var(--ds-text). Готовый цвет с решёткой, произвольный радиус и размер шрифта вне шкалы ловит scripts/ds-lint.mjs на предкоммитном хуке. Новый общий элемент кладём в app/_ds/ds.css и обязательно показываем здесь, иначе система расходится с сайтами. Полные правила: docs/UI_RULES.md.