Skip to content
PПромтбук
RUEN
01Аудит

Аудит паритета UI: каждый паттерн одинаков везде

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

Дизайн-системы не ломаются внезапно — они дрейфуют. Один разработчик дописал border-radius: 6px вместо var(--radius-sm), другой скопировал кнопку и забыл про hover-цвет. Через полгода в продукте живёт 11 вариантов border-radius там где система определяет 3. Пользователь не назовёт это «несоответствием токенов», но чувствует что что-то «не так».

Этот аудит — не «посмотри как выглядит», а структурированное доказательство: для каждого повторяющегося паттерна — список всех его экземпляров, извлечённые computed-значения, сравнение с single source of truth, дивергенции с точным severity и конкретным фиксом.

Product: {{product_url}} Tokens source: {{tokens_source}}

1. Parity rule: когда два экземпляра ДОЛЖНЫ быть идентичны

Прежде чем проверять — определи правило паритета для каждого паттерна. Без него «различие» и «намеренный вариант» неразличимы.

Формат parity rule:

"Все primary buttons одного размера должны иметь одинаковый background-color, border-radius, font-size, font-weight, padding, hover-color, focus-ring и disabled-opacity — независимо от страницы и контейнера."

  • ✓ Primary button на /login vs primary button в modal — должны совпадать (одна роль, один паттерн)
  • ✓ Card с border на /dashboard vs card на /settings — должны совпадать
  • ✗ Primary button vs secondary button — намеренно разные, paritу rule не применяется
  • ✗ Skeleton на /feed vs skeleton на /profile — одинаковый компонент, одна реализация

Для каждого паттерна в sections 2-4: сначала запиши parity rule, потом inventory.

2. Inventory паттернов: что проверять

2.1 Кнопки

Для каждого варианта (primary, secondary, ghost, destructive, link-style) × каждого размера (sm, md, lg, icon):

Извлечь по каждому экземпляру:

button.primary.md {
  background-color: computed → должен быть var(--color-primary-600)
  color: computed
  border-radius: computed → должен быть var(--radius-md)
  padding: computed
  font-size: computed → должен быть var(--text-sm)
  font-weight: computed
  transition: computed → должен быть var(--duration-fast) ease
}
:hoverbackground-color
:focus-visibleoutline, outline-offset
:disabledopacity, cursor, pointer-events

Как извлечь: DevTools → Elements → Computed → скопировать ключевые свойства. Для sweep по всему сайту: document.querySelectorAll('[class*="btn"],[class*="button"]') → getComputedStyle на каждом элементе → сгруппировать по variant+size.

Красные флаги:

  • ✓ 3 экземпляра primary.md на странице — одинаковые computed значения → pass
  • ✗ 11 distinct border-radius values при 3 задокументированных → divergence

2.2 Inputs и формы

Каждое поле: text, email, password, textarea, select, checkbox, radio, toggle, file-upload, datepicker.

Проверить:

  • border-color в default / focus / error / disabled состояниях → единый токен
  • border-radius → единый
  • padding → единый
  • label: позиция (over/above/inside), font-size, color
  • placeholder color → var(--color-muted) или хардкод?
  • error message: color, font-size, gap от поля
  • focus ring: одинаковый ли outline на всех полях и всех страницах?

2.3 Modals и диалоги

  • border-radius → один ли токен
  • backdrop: opacity, color, blur
  • padding inside
  • close button: позиция, размер, hover-цвет
  • ESC/outside-click dismiss — единое поведение везде?
  • Анимация входа/выхода: duration, easing

2.4 Cards

  • border-radius → единый для карточек одного типа
  • border или box-shadow: хватает одного, не оба ли используются вперемешку
  • padding → var(--space-4) везде или 16px / 1rem / p-4 в разных местах?
  • hover-state: lift (translateY), shadow-change, border-color-change — или их микс?

2.5 Empty states

  • иллюстрация vs иконка: единый подход или рандомно?
  • heading + body copy: единый размер и weight
  • CTA: primary button или link — единый паттерн?
  • Отступы от краёв контейнера

2.6 Skeletons / loaders

  • Pulse-анимация: duration, easing → единые по всему продукту
  • border-radius skeleton-блоков: совпадает ли с реальными элементами?
  • Spinner: единый размер и цвет везде (не 3 разных компонента spinner)

2.7 Toasts / уведомления

  • success / error / warning / info: цвет иконки + border-left + bg — единая палитра
  • Позиция (bottom-right? top-center?): одна для всего продукта
  • Duration: 3 с / 5 с / indefinite — единое правило
  • Dismiss: кнопка, свайп, авто — единообразие

3. Токены: drift-детектор

Самый частый источник дивергенции — хардкод вместо токена. Даже правильное значение (#3B82F6 вместо var(--color-primary-500)) — это drift.

Как искать token drift

Метод 1: computed vs var() — в DevTools смотри Computed, не Styles. Если видишь color: rgb(59, 130, 246) — определи, это через var() или хардкод. В Styles-панели ищи источник: если color: #3b82f6 без ссылки на переменную — drift.

Метод 2: CSS grep — в исходниках:

# Хардкод hex-цветов вне токенного файла:
grep -r '#[0-9a-fA-F]\{3,6\}' src/components --include="*.css" --include="*.module.css"

# px-значения отступов (признак дрейфа от spacing-scale):
grep -r 'padding:\s*[0-9]\+px\|margin:\s*[0-9]\+px' src/components

# Хардкод border-radius:
grep -r 'border-radius:\s*[0-9]' src/components

Метод 3: Figma inspect — если {{tokens_source}} — Figma: проверить через Dev Mode, что применённые токены совпадают с реализованными.

Token drift severity

Тип driftSeverityПример
Правильное значение, хардкодLowborder-radius: 8px вместо var(--radius-md) → сейчас совпадает, сломается при изменении токена
Близкое значение, другой токенMediumvar(--radius-sm) где должен быть var(--radius-md) → 4px vs 8px, видно глазу
Другое значение, хардкодHighborder-radius: 12px — нет такого токена, ни один компонент кроме этого не использует
Другой цвет, хардкодCriticalbackground: #2563EB вместо var(--color-primary-600) — пройдёт мимо color-theme change

4. Spacing, typography, motion, icons

Spacing scale

Документированная scale, например: 4, 8, 12, 16, 20, 24, 32, 40, 48, 64px.

Инвентаризовать:

  • gap between cards на /home
  • padding inside each card
  • margin-bottom под headings
  • gap в nav links
  • padding в модальных окнах

Ожидаемый результат: все значения принадлежат scale. Красный флаг: gap: 18px / padding: 22px — off-scale, нет такого токена.

Как считать distinct values:

// В DevTools console на странице:
[...document.querySelectorAll('*')].map(el => getComputedStyle(el).gap).filter(v => v && v !== 'normal').reduce((acc, v) => { acc[v] = (acc[v]||0)+1; return acc; }, {})

Type scale

Проверить: сколько distinct font-size values в продукте vs сколько определено в системе.

Допустимые: xs (12px), sm (14px), base (16px), lg (18px), xl (20px), 2xl (24px), 3xl (30px), 4xl (36px).

Красный флаг: 13px, 15px, 17px, 19px — off-scale. Сколько их и где?

ЗначениеКол-во элементовФайлыСоответствует токену?
14px47button, label, nav✓ var(--text-sm)
13px3/pricing table✗ нет такого токена
15px1footer copyright✗ нет такого токена

Icon stroke и size

  • Один ли набор иконок? (не lucide + heroicons вперемешку)
  • stroke-width: один ли (1.5 везде или 2 везде, не смесь)?
  • Размер: 16px / 20px / 24px — соответствует scale или рандомный?
  • Цвет: через currentColor или хардкод?

Motion

  • transition-duration: сколько distinct values? Система определяет 3 (fast 100ms, base 200ms, slow 400ms)?
  • easing functions: ease / ease-in-out / cubic-bezier(...) — один ли паттерн?
  • Анимации входа (fade-in, slide-up): одинаковы ли на разных компонентах?

5. Microcopy и лейблы

Паrity rule для copy: один и тот же action называется одним словом везде.

Inventory:

  • Кнопка отмены: «Отмена» / «Отменить» / «Cancel» / «Закрыть» — сколько вариантов?
  • Destructive confirm: «Удалить» / «Удалить навсегда» / «Delete» — единый ли?
  • Empty state copy: «Ничего нет» / «Пусто» / «Нет данных» / «Список пуст»
  • Loading copy: «Загрузка...» / «Загружаем...» / ничего
  • Error copy: «Что-то пошло не так» / «Ошибка» / «Попробуйте позже»

Таблица:

ActionVariant 1Variant 2CanonicalOffending locations
Cancel button«Отмена» (32 вхождения)«Отменить» (4 вхождения)«Отмена»/settings/profile, /billing
Delete confirm«Удалить» (8)«Удалить навсегда» (2)«Удалить»/admin/users

6. Как решать, что canonical

Когда два экземпляра расходятся и неясно какой правильный:

  1. Токен в {{tokens_source}} → он canonical. Экземпляр без токена — дрейфует.
  2. Если оба без токена → canonical = чаще встречающийся (majority wins) или определённый дизайнером.
  3. Если расходятся визуально и оба «выглядят нормально» → canonical = тот, что в более видимом, высокотрафиковом месте (main CTA, home page).
  4. Если нет консенсуса → завести токен в {{tokens_source}}, зафиксировать значение, привести все к нему.

7. Divergence Ledger

Финальный реестр расхождений:

PatternRuleInstance AInstance BA valueB valueCanonicalSeverityFix
Primary button radiusвсе одинаковые/login CTA/modal CTA8px (var)6px (hardcode)8pxMediumзаменить 6px на var(--radius-md) в Modal.tsx:L42
Gap between cardsspacing scale/home feed/search results24px18px24pxHigh/search/Results.tsx: gap-[18px] → gap-6
Button font-weightвсе primary 600/home hero/pricing table600500600LowPricingCard.tsx:L88
Spinner size24px everywhere/dashboard/settings save24px20px24pxLowSettingsForm.tsx

Severity scale:

  • Critical — разные бренд-цвета, разные паттерны confirm для destructive action
  • High — off-scale spacing, заметное расхождение border-radius
  • Medium — close but wrong (8px vs 6px), правильное значение но хардкод
  • Low — copy-вариации, off-by-one font-weight

Anti-patterns

  • ❌ Проверять «на глаз» без computed values — 8px vs 6px неотличимы без цифр
  • ❌ Игнорировать hover/focus/disabled состояния — drift чаще всего именно там
  • ❌ Считать правильное значение без var() «ОК» — это drift, ломается при смене токена
  • ❌ Составлять Ledger без fix-пути к конкретному файлу и строке — бесполезен для разработчика
  • ❌ Делать аудит без parity rule — каждый «нашёл» будет спорить что это «намеренный вариант»
  • ❌ Смешивать намеренные варианты (primary vs secondary) с дрейфом — засоряет Ledger
  • ❌ Забывать mobile viewport — на мобильном отступы часто перегружены отдельными медиазапросами с off-scale значениями
  • ❌ Проверять только CSS, не checking Figma — drift между документацией и реализацией важнее
  • ❌ Не считать количество distinct values — «разные» без числа не конвертируются в приоритет

Output

  1. Parity rules doc — список паттернов × их правило паритета (что должно совпадать)
  2. Token drift report — harcoded values vs var(), count per file, severity
  3. Spacing distinct-values dump — вывод console-скрипта + annotated список off-scale значений
  4. Type scale violations — таблица font-size values с количеством и файлами
  5. Divergence Ledger — полная таблица: pattern × instance A × instance B × canonical × severity × fix-path
  6. Critical fixes list — топ-10 по severity для первого PR
  7. Token backfill plan — какие хардкод-значения нужно завести как токен в {{tokens_source}}
К подразделу «Аудит»
Похожие промты