Действуй как principal design-systems инженер. Спроектируй density-режимы для всего продукта по образцу Linear и Notion. Default: {{default_mode}}. Preference хранится по ключу {{storage_key}}.
1. Зачем три режима
- comfortable — дефолт для новых пользователей, презентации, дашборды для нерегулярных юзеров. Дыхание, читаемость, иерархия. ~16-20% больше воздуха.
- cozy — золотая середина. Большинство SaaS-продуктов дефолтят сюда после первой недели. Производительность + комфорт.
- compact — для power users. Linear-режим. Видно больше данных на экран, оператор работает быстрее, но новичка пугает.
Принцип: density меняет только то, что относится к воздуху. Размеры самих сущностей (иконки, аватары, размер шрифта основного текста) — не трогаем. Меняем gap, padding, vertical rhythm, row heights в таблицах.
2. Token strategy: space scale × density factor
База — space scale в CSS-переменных. Density factor умножает spacing-токены, не задевая всё остальное.
:root {
/* Base space scale (cozy = 1.0) */
--space-base-1: 4px;
--space-base-2: 8px;
--space-base-3: 12px;
--space-base-4: 16px;
--space-base-6: 24px;
--space-base-8: 32px;
/* Density factor */
--density-factor: 1;
/* Public spacing tokens */
--space-1: calc(var(--space-base-1) * var(--density-factor));
--space-2: calc(var(--space-base-2) * var(--density-factor));
--space-3: calc(var(--space-base-3) * var(--density-factor));
--space-4: calc(var(--space-base-4) * var(--density-factor));
--space-6: calc(var(--space-base-6) * var(--density-factor));
--space-8: calc(var(--space-base-8) * var(--density-factor));
/* Row heights в таблицах */
--row-height-base: 36px;
--row-height: calc(var(--row-height-base) * var(--density-factor));
}
[data-density="comfortable"] { --density-factor: 1.2; }
[data-density="cozy"] { --density-factor: 1; }
[data-density="compact"] { --density-factor: 0.78; }
Применяется одним атрибутом на <html>:
<html data-density="compact">
3. Что НЕ масштабировать
Категорически нельзя умножать на density factor:
- Touch targets — минимум 44×44 (iOS) / 48×48 (Android), даже в compact. Иначе сломан тач.
- Focus rings — толщина и offset фикс. Если в compact кольцо становится 1.5px вместо 2px, accessibility ломается.
- Иконки — 16/20/24px дискретно, по семантической роли, а не по плотности. Иконка inline-текста — 16px всегда.
- Шрифты body / основного контента — читаемость не должна зависеть от density. Меняем только UI chrome (метки в таблице, helper text).
- Border-radius — карточка остаётся карточкой. Радиус не пропорционален плотности.
- Border width — 1px в cozy = 1px в compact. Иначе hairlines исчезают.
- Skeleton / shimmer animations — длительность фикс.
Хорошее правило: если элемент несёт смысл сам по себе (иконка, кнопка, текст) — фикс. Если это пустое пространство между элементами — масштабируем.
4. Per-component overrides
Не каждый компонент масштабируется одинаково. Дай управление через атрибут:
/* Кнопка: padding меняется, но не радикально — кнопка не должна стать невидимо тонкой */
.btn {
padding-block: calc(var(--space-base-2) * clamp(0.85, var(--density-factor), 1.15));
padding-inline: calc(var(--space-base-4) * clamp(0.85, var(--density-factor), 1.15));
}
/* Таблица: row-height чувствительнее, можно жать сильнее */
.table-row {
block-size: calc(var(--row-height-base) * var(--density-factor));
}
/* Sidebar item: специальный compact-режим */
[data-density="compact"] .sidebar-item { padding-block: 4px; gap: 6px; }
5. Сохранение preference
Стек: cookie (SSR) + localStorage (быстрый клиентский read) + system event.
// 1. На SSR читаем cookie, проставляем data-density до hydration → no flash
// 2. Клиент: при изменении пишем в localStorage И в cookie
// 3. Reactивно меняем атрибут на <html>
const setDensity = (mode: 'comfortable' | 'cozy' | 'compact') => {
document.documentElement.dataset.density = mode;
localStorage.setItem('{{storage_key}}', mode);
document.cookie = '{{storage_key}}=' + mode + '; path=/; max-age=31536000; SameSite=Lax';
};
// На загрузке
const saved = localStorage.getItem('{{storage_key}}') ?? '{{default_mode}}';
document.documentElement.dataset.density = saved;
Anti-flash: в SSR-фреймворке (Next.js, Astro) прочитай cookie на сервере и поставь data-density прямо в HTML — иначе на первый кадр пользователь увидит дефолт, потом дёрг.
Cross-tab sync:
window.addEventListener('storage', (e) => {
if (e.key === '{{storage_key}}' && e.newValue) {
document.documentElement.dataset.density = e.newValue;
}
});
6. UI переключателя
Не закапывай в settings. Linear показывает в command palette (Cmd+K → "density"), Notion — в appearance settings. Лучше — в settings menu рядом с темой.
Три варианта visualization:
- Сегментированный контрол (3 кнопки)
- Slider (если planируешь больше уровней потом)
- Preview cards — мини-mockup интерфейса в каждом режиме
7. Формат вывода
## Density system
### Tokens
| Token | comfortable | cozy | compact |
|---|---|---|---|
| --space-4 | 19.2px | 16px | 12.5px |
| --row-height | 43px | 36px | 28px |
### Не масштабируем
- Touch targets (≥ 44×44)
- Focus rings (2px / 2px offset)
- Icons (16/20/24 дискретно)
- Body font-size
### Persistence
- Cookie + localStorage + storage event
- SSR reads cookie → no flash
### UI
- Command palette + settings panel
- 3 preview cards
Anti-patterns
- ❌ Density factor умножает font-size body — текст в compact = 12.5px, читаемость провалена.
- ❌ Touch targets масштабируются вместе со spacing — в compact кнопки 32×32, мобильные пользователи мажут пальцами.
- ❌ Focus ring становится 1.2px в compact — accessibility-аудит проваливается мгновенно.
- ❌ Только localStorage без cookie — SSR-рендер всегда выдаёт default, при загрузке дёрг.
- ❌ Density через перезагрузку страницы — UX 2010. Меняй мгновенно через CSS-переменную.
- ❌ Два отдельных набора classes (
btn-cozy,btn-compact) — bundle×3, mental load, рассинхрон. - ❌ Border-width умножается на factor — в compact hairlines становятся 0.78px, на retina выглядят дрожащими.
- ❌ Без cross-tab sync — пользователь меняет в одной вкладке, в другой по-старому, фрустрирует.
- ❌ Сохранение в URL (
?density=compact) — расшаривание ссылок ломает плотность у получателя. - ❌ Default = compact для нового юзера — отпугивает 80% аудитории за первые 30 секунд.
Аудит brand-consistency
Прогон интерфейса на согласованность: цвета, spacing, typography, voice, иконки. Найти отклонения от системы.
Mobile-friendly аудит (375px)
Аудит мобильной версии на iPhone SE: тач-таргеты, скролл, попапы, tap-vs-hover, input zoom.
Brand guidelines с нуля
Сборка полного гайдлайна: voice, color tokens, типографика, правила логотипа, антипаттерны и примеры. Готовый DESIGN.md.