Дизайн-система — это общий для дизайна и кода набор именованных решений (токенов), компонентов и правил их использования. Практичный старт — два слоя токенов: базовые хранят палитру и шкалы, семантические называют роли («акцент», «текст второго плана») и переключаются в тёмной теме. Токены записывают в JSON по формату W3C DTCG и генерируют из него CSS-переменные, которые читают компоненты.
После материала вы сможете
- описать цвет, типографику, отступы, радиусы и движение токенами в формате DTCG 2025.10 в два слоя — базовый и семантический
- собрать из JSON файл с CSS custom properties и убедиться, что битая ссылка останавливает сборку
- сделать компонент без единого жёсткого значения и переключить его в тёмную тему правкой только семантического слоя
- сопоставить слои токенов с коллекциями и режимами Figma Variables
Что знать заранее
Проверено на
Design Tokens Format Module 2025.10 (W3C DTCG)Node.js 24.18Microsoft Edge 155 (Playwright 1.64)
Сверено с документацией: 11 октября 2026 г.
Содержание
Что входит в дизайн-систему и когда она окупается
Nielsen Norman Group определяет дизайн-систему как полный набор стандартов, который помогает управлять дизайном в масштабе за счёт переиспользуемых компонентов и паттернов. На практике это не «файл в Figma с кнопками», а договорённость между дизайном и кодом: у каждого решения — цвета, отступа, радиуса, длительности анимации — есть имя, а у каждого компонента — правила, когда и как его применять.
Стиль-гайд
- Что в ней
- Бренд, тон текста, визуальные и интерактивные стандарты, принципы
- Пример
- «Кнопки называем глаголом: „Записаться“, а не „ОК“»
Библиотека компонентов
- Что в ней
- Переиспользуемые элементы с названиями, описанием, атрибутами, состояниями и кодом
- Пример
- Кнопка: основная, вторичная, наведение, фокус
Библиотека паттернов
- Что в ней
- Группы элементов и шаблоны экранов
- Пример
- Форма записи, карточка услуги в каталоге
Токены
- Что в ней
- Именованные значения, из которых собраны все три части
- Пример
- color.accent, space.md, radius.lg
Когда это окупается. По оценке NN/g, дизайн-система лучше всего работает, когда компания видит впереди годы повторяемой дизайн-работы, а для раннего прототипа полная система вряд ли быстро окупится; к тому же её создание и поддержка требуют выделенной команды. Material Design даёт похожий фильтр для токенов: они мало помогут приложению с жёстко прописанными значениями, которое не собираются менять в ближайшие год-два.
Токены: базовые и семантические
Material Design называет токены маленькими переиспользуемыми дизайн-решениями: вместо значения #E8DEF8 — понятное имя вроде md.ref.palette.secondary90. Если макет дизайнера и код разработчика ссылаются на одно имя, они гарантированно используют один цвет, даже когда его значение поменяют.
Минимальная рабочая схема — два слоя. Базовые токены (у Material — reference) хранят все допустимые значения и ничего не знают о том, где они используются: base.color.brand-600, base.color.neutral-50. Семантические (у Material — system) описывают роль в интерфейсе: color.accent, color.text-muted, color.surface. Именно семантический слой переключается в тёмной теме, а Material подчёркивает: здесь и происходит темизация. Третий класс у Material — компонентные токены (md.comp…), которые указывают на системные; в нашем примере этот слой живёт прямо в CSS компонента.
- 01Базовыеbase.color.brand-600 = #3d4fd6 — палитра и шкалы, без смысла
- 02Семантическиеcolor.accent → base.color.brand-600 — роль; меняется по теме
- 03Компонентные--button-bg → color.accent — свойства одного компонента
- 04Компонент.button читает только свои переменные
Имя по внешнему виду
- --blue, --gray-light, --padding-16
- Бренд стал зелёным — переменная --blue теперь зелёная
- Непонятно, можно ли брать --gray-light для текста
- Тёмная тема требует новых переменных и правки компонентов
Имя по роли
- --color-accent, --color-text-muted, --space-md
- Смена бренда — одна ссылка в семантическом слое
- Роль подсказывает, где токен уместен
- Тёмная тема — другой набор значений тех же имён
Тот же принцип — в Apple Human Interface Guidelines: динамические системные цвета определены по назначению, а не по внешнему виду, и Apple просит не переопределять их смысл — например, не использовать цвет разделителя как цвет текста. Это правило стоит перенести и на свои семантические токены.
Шаг 1. Записываем токены в формате W3C DTCG
Design Tokens Community Group при W3C 28 октября 2025 года опубликовала первую стабильную версию формата — Design Tokens Format Module 2025.10. Это отчёт сообщества (Final Community Group Report): документ считается стабильным, но не является стандартом W3C. Его ценность в том, что один и тот же JSON могут читать разные инструменты — плагины Figma, генераторы кода, документация.
- Файл — обычный JSON, рекомендуемые расширения
.tokensи.tokens.json - Токен — объект со свойством
$value; объект без$value— группа $typeможно задать группе — его унаследуют все токены внутри- Ссылка на другой токен записывается в фигурных скобках:
"{base.color.brand-600}" - Имена не начинаются с
$и не содержат точку и фигурные скобки — точка служит разделителем пути $description— пояснение,$deprecated— пометка устаревшего токена
{
"base": {
"color": {
"$type": "color",
"neutral-0": { "$value": { "colorSpace": "srgb", "components": [1, 1, 1], "hex": "#ffffff" } },
"neutral-50": { "$value": { "colorSpace": "srgb", "components": [0.961, 0.965, 0.973], "hex": "#f5f6f8" } },
"neutral-200": { "$value": { "colorSpace": "srgb", "components": [0.867, 0.882, 0.906], "hex": "#dde1e7" } },
"neutral-300": { "$value": { "colorSpace": "srgb", "components": [0.769, 0.792, 0.827], "hex": "#c4cad3" } },
"neutral-600": { "$value": { "colorSpace": "srgb", "components": [0.294, 0.333, 0.388], "hex": "#4b5563" } },
"neutral-800": { "$value": { "colorSpace": "srgb", "components": [0.122, 0.149, 0.2], "hex": "#1f2633" } },
"neutral-900": { "$value": { "colorSpace": "srgb", "components": [0.078, 0.102, 0.141], "hex": "#141a24" } },
"neutral-950": { "$value": { "colorSpace": "srgb", "components": [0.047, 0.063, 0.094], "hex": "#0c1018" } },
"brand-200": { "$value": { "colorSpace": "srgb", "components": [0.78, 0.804, 1], "hex": "#c7cdff" } },
"brand-300": { "$value": { "colorSpace": "srgb", "components": [0.647, 0.69, 1], "hex": "#a5b0ff" } },
"brand-600": { "$value": { "colorSpace": "srgb", "components": [0.239, 0.31, 0.839], "hex": "#3d4fd6" } },
"brand-700": { "$value": { "colorSpace": "srgb", "components": [0.184, 0.247, 0.722], "hex": "#2f3fb8" } },
"red-300": { "$value": { "colorSpace": "srgb", "components": [1, 0.608, 0.58], "hex": "#ff9b94" } },
"red-600": { "$value": { "colorSpace": "srgb", "components": [0.769, 0.169, 0.169], "hex": "#c42b2b" } }
}
},
"color": {
"$type": "color",
"$description": "Семантические цвета: компоненты берут только их",
"bg": { "$value": "{base.color.neutral-0}" },
"surface": { "$value": "{base.color.neutral-50}" },
"border": { "$value": "{base.color.neutral-200}" },
"text": { "$value": "{base.color.neutral-900}" },
"text-muted": { "$value": "{base.color.neutral-600}" },
"accent": { "$value": "{base.color.brand-600}" },
"accent-hover": { "$value": "{base.color.brand-700}" },
"on-accent": { "$value": "{base.color.neutral-0}" },
"danger": { "$value": "{base.color.red-600}" }
},
"space": {
"$type": "dimension",
"xs": { "$value": { "value": 4, "unit": "px" } },
"sm": { "$value": { "value": 8, "unit": "px" } },
"md": { "$value": { "value": 16, "unit": "px" } },
"lg": { "$value": { "value": 24, "unit": "px" } },
"xl": { "$value": { "value": 32, "unit": "px" } }
},
"radius": {
"$type": "dimension",
"sm": { "$value": { "value": 6, "unit": "px" } },
"md": { "$value": { "value": 10, "unit": "px" } },
"lg": { "$value": { "value": 16, "unit": "px" } }
},
"font": {
"family": {
"$type": "fontFamily",
"sans": { "$value": ["Inter", "Segoe UI", "Arial", "sans-serif"] }
},
"size": {
"$type": "dimension",
"body": { "$value": { "value": 1, "unit": "rem" } },
"small": { "$value": { "value": 0.875, "unit": "rem" } },
"title": { "$value": { "value": 1.25, "unit": "rem" } }
},
"weight": {
"$type": "fontWeight",
"regular": { "$value": 400 },
"bold": { "$value": 600 }
},
"line-height": {
"$type": "number",
"body": { "$value": 1.5 }
}
},
"motion": {
"duration": {
"$type": "duration",
"fast": { "$value": { "value": 150, "unit": "ms" } }
},
"easing": {
"$type": "cubicBezier",
"standard": { "$value": [0.2, 0, 0, 1] }
}
}
}color
- Формат $value
- Объект: colorSpace, components (0–1), необязательные alpha и hex — запасное значение
- Пример
- { "colorSpace": "srgb", "components": [1, 1, 1], "hex": "#ffffff" }
dimension
- Формат $value
- Объект value + unit, единица только px или rem
- Пример
- { "value": 16, "unit": "px" }
fontFamily
- Формат $value
- Строка или массив имён в порядке приоритета
- Пример
- ["Inter", "Segoe UI", "Arial", "sans-serif"]
fontWeight
- Формат $value
- Число от 1 до 1000 или псевдоним: regular, semi-bold, bold…
- Пример
- 600
number
- Формат $value
- Число JSON
- Пример
- 1.5 — межстрочный интервал
duration
- Формат $value
- Объект value + unit, единица ms или s
- Пример
- { "value": 150, "unit": "ms" }
cubicBezier
- Формат $value
- Четыре числа [x1, y1, x2, y2], x — от 0 до 1
- Пример
- [0.2, 0, 0, 1]
Шаг 2. Переводим JSON в CSS custom properties
Браузер не читает JSON токенов — ему нужны CSS custom properties, их ещё называют CSS-переменными. Как описывает MDN, имя такого свойства начинается с двух дефисов, значение читается функцией var(), а объявление на :root делает переменную доступной всей странице. Имена чувствительны к регистру: --color-text и --Color-text — разные переменные. Значение наследуется потомками.
Скрипт ниже работает в Node.js 24 без зависимостей. Он обходит группы, наследует $type от группы, превращает ссылки в var() и проверяет, что каждая ссылка ведёт на существующий токен.
// Node.js 24, без зависимостей: tokens/*.json (формат DTCG 2025.10) → tokens.css
import { readFileSync, writeFileSync } from "node:fs";
const read = (file) => JSON.parse(readFileSync(file, "utf8"));
const GENERIC_FONTS = new Set(["serif", "sans-serif", "monospace", "system-ui", "cursive"]);
// путь в JSON → имя CSS-переменной: ["color", "text-muted"] → "--color-text-muted"
const cssName = (path) => `--${path.join("-")}`;
// "{base.color.neutral-0}" → "var(--base-color-neutral-0)"
function alias(value) {
if (typeof value !== "string" || !/^\{[^{}]+\}$/.test(value)) return null;
return `var(${cssName(value.slice(1, -1).split("."))})`;
}
function toCss(type, value) {
const ref = alias(value);
if (ref) return ref;
switch (type) {
case "color": {
const alpha = value.alpha ?? 1;
if (value.hex && alpha === 1) return value.hex;
return `color(${value.colorSpace} ${value.components.join(" ")} / ${alpha})`;
}
case "dimension":
case "duration":
return `${value.value}${value.unit}`;
case "fontFamily":
return [value].flat().map((f) => (GENERIC_FONTS.has(f) ? f : `"${f}"`)).join(", ");
case "fontWeight":
case "number":
return String(value);
case "cubicBezier":
return `cubic-bezier(${value.join(", ")})`;
default:
throw new Error(`Тип «${type}» не поддержан`);
}
}
// обходит группы; $type наследуется от ближайшей группы
function collect(node, path = [], type, out = new Map()) {
for (const [key, child] of Object.entries(node)) {
if (key.startsWith("$")) continue;
const childType = child.$type ?? type;
if ("$value" in child) out.set(cssName([...path, key]), toCss(childType, child.$value));
else collect(child, [...path, key], childType, out);
}
return out;
}
const light = collect(read("tokens/tokens.json"));
const dark = collect(read("tokens/tokens.dark.json"));
// каждая ссылка должна вести на существующий токен
for (const [name, value] of [...light, ...dark]) {
const target = value.match(/^var\((--[^)]+)\)$/)?.[1];
if (target && !light.has(target)) throw new Error(`${name}: нет токена ${target}`);
}
const block = (vars, pad = " ") => [...vars].map(([n, v]) => `${pad}${n}: ${v};`).join("\n");
const css = `/* Сгенерировано build-tokens.mjs — не править вручную */
:root {
${block(light)}
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
${block(dark, " ")}
}
}
:root[data-theme="dark"] {
${block(dark)}
}
`;
writeFileSync("tokens.css", css);
console.log(`tokens.css: ${light.size} токенов, тёмная тема переопределяет ${dark.size}`);$ node build-tokens.mjs
tokens.css: 40 токенов, тёмная тема переопределяет 9/* Сгенерировано build-tokens.mjs — не править вручную */
:root {
--base-color-neutral-0: #ffffff;
/* … базовые токены … */
--color-bg: var(--base-color-neutral-0);
--color-surface: var(--base-color-neutral-50);
--color-border: var(--base-color-neutral-200);
--color-text: var(--base-color-neutral-900);
--color-text-muted: var(--base-color-neutral-600);
--color-accent: var(--base-color-brand-600);
--color-accent-hover: var(--base-color-brand-700);
--color-on-accent: var(--base-color-neutral-0);
--color-danger: var(--base-color-red-600);
/* … отступы, радиусы, шрифт, движение … */
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-bg: var(--base-color-neutral-950);
--color-surface: var(--base-color-neutral-900);
/* … остальные семантические цвета … */
}
}
/* … и тот же набор в :root[data-theme="dark"] */Читает два файла
tokens.json — все токены и светлые значения ролей, tokens.dark.json — только роли, которые меняются в тёмной теме.
Строит имена
Путь в JSON склеивается через дефис: color → text-muted превращается в --color-text-muted.
Сохраняет ссылки
"{base.color.brand-600}" становится var(--base-color-brand-600) — связь слоёв остаётся видна в коде и в DevTools.
Проверяет целостность
Мы подставили в тёмную тему несуществующий neutral-1000 — сборка остановилась с ошибкой, а не выпустила CSS с пустой переменной.
Пишет три блока
:root со светлыми значениями, медиазапрос для системной тёмной темы и :root[data-theme="dark"] для выбора вручную.
Шаг 3. Компонент, который знает только токены
В стилях компонента нет ни одного hex-значения и ни одного «магического» числа: цвета, отступы, радиусы, шрифт и длительность перехода приходят из токенов. У кнопки свой маленький слой — --button-bg, --button-fg, --button-border. Вторичный вариант и наведение меняют только эти три переменные, а не переписывают свойства заново, поэтому новый вариант — это три строки.
body {
margin: 0;
padding: var(--space-xl);
background: var(--color-bg);
color: var(--color-text);
font-family: var(--font-family-sans);
font-size: var(--font-size-body);
line-height: var(--font-line-height-body);
}
.card {
max-width: 360px;
padding: var(--space-lg);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background: var(--color-surface);
}
.card__title {
margin: 0 0 var(--space-sm);
font-size: var(--font-size-title);
font-weight: var(--font-weight-bold);
}
.card__text {
margin: 0 0 var(--space-md);
color: var(--color-text-muted);
}
.card__actions {
display: flex;
gap: var(--space-sm);
}
/* компонентные токены: кнопка читает только свои переменные */
.button {
--button-bg: var(--color-accent);
--button-fg: var(--color-on-accent);
--button-border: var(--color-accent);
display: inline-flex;
align-items: center;
min-height: 44px;
padding: 0 var(--space-md);
border: 1px solid var(--button-border);
border-radius: var(--radius-md);
background: var(--button-bg);
color: var(--button-fg);
font: inherit;
font-weight: var(--font-weight-bold);
cursor: pointer;
transition: background-color var(--motion-duration-fast) var(--motion-easing-standard);
}
.button:hover {
--button-bg: var(--color-accent-hover);
--button-border: var(--color-accent-hover);
}
.button:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
.button--secondary,
.button--secondary:hover {
--button-bg: transparent;
--button-fg: var(--color-accent);
--button-border: var(--color-border);
}
.button--secondary:hover {
--button-bg: var(--color-bg);
}<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Токены в деле</title>
<link rel="stylesheet" href="tokens.css" />
<link rel="stylesheet" href="components.css" />
</head>
<body>
<article class="card">
<h2 class="card__title">Запись на консультацию</h2>
<p class="card__text">Все цвета, отступы и радиусы этой карточки берутся из токенов.</p>
<div class="card__actions">
<button class="button" type="button">Записаться</button>
<button class="button button--secondary" type="button">Подробнее</button>
</div>
</article>
</body>
</html>Как проверить результат. Запустите node build-tokens.mjs, откройте index.html и в DevTools посмотрите вкладку Computed у основной кнопки: background-color должен быть rgb(61, 79, 214) — значение base.color.brand-600, прошедшее через color.accent и --button-bg. Мы прогнали эту страницу в headless Microsoft Edge через Playwright: светлая и тёмная системная тема, ручной data-theme в обе стороны, наведение и проверка ссылок — все 9 автоматических проверок прошли.
Тёмная тема через семантические токены
Тёмная тема — это второй набор значений тех же семантических токенов. Базовая палитра и компоненты не меняются: файл ниже переопределяет девять ролей и больше ничего.
{
"color": {
"$type": "color",
"bg": { "$value": "{base.color.neutral-950}" },
"surface": { "$value": "{base.color.neutral-900}" },
"border": { "$value": "{base.color.neutral-800}" },
"text": { "$value": "{base.color.neutral-50}" },
"text-muted": { "$value": "{base.color.neutral-300}" },
"accent": { "$value": "{base.color.brand-300}" },
"accent-hover": { "$value": "{base.color.brand-200}" },
"on-accent": { "$value": "{base.color.neutral-950}" },
"danger": { "$value": "{base.color.red-300}" }
}
}В tokens.css тёмные значения попадают дважды. Медиазапрос prefers-color-scheme: dark с селектором :root:not([data-theme="light"]) следует системной настройке, если человек не выбрал светлую тему явно. Блок :root[data-theme="dark"] включает тёмную тему по выбору — достаточно выполнить document.documentElement.dataset.theme = "dark" в обработчике переключателя.
text / bg
- Светлая тема
- 17,46:1
- Тёмная тема
- 17,61:1
text-muted / surface
- Светлая тема
- 6,99:1
- Тёмная тема
- 10,59:1
on-accent / accent
- Светлая тема
- 6,39:1
- Тёмная тема
- 9,30:1
accent / surface
- Светлая тема
- 5,91:1
- Тёмная тема
- 8,52:1
danger / bg
- Светлая тема
- 5,63:1
- Тёмная тема
- 9,40:1
В Figma та же структура собирается из переменных. По справке Figma, коллекция — это набор переменных и режимов; режим хранит по одному значению каждой переменной для своего контекста — светлой и тёмной темы, языка, размера устройства. Переменная может ссылаться на другую переменную того же типа — это и есть алиас. Типы: цвет, число, строка, логическое значение, а для Figma Motion ещё длительность и easing. Удобное соответствие: коллекция «Base» без режимов повторяет группу base, коллекция «Semantic» с режимами Light и Dark — файлы tokens.json и tokens.dark.json.
У DTCG для тем есть отдельный документ — Resolver Module 2025.10. Он описывает наборы токенов и модификаторы с контекстами (например, light и dark) в файле .resolver.json. Наша пара файлов и скрипт — упрощённая версия той же идеи; если ваш инструмент поддерживает resolver, описывайте темы через него.
Документация, версии и минимальный набор
Система, которую никто не ведёт, расходится с продуктом за несколько релизов. NN/g подчёркивает, что дизайн-система требует постоянной поддержки, и называет минимальную команду: дизайнер взаимодействия, визуальный дизайнер и разработчик. Документация при этом не обязана быть отдельным порталом: на старте хватает таблицы компонентов со статусами и ссылками на токены.
Кнопка
- Статус
- Готов
- Токены
- --button-*, color.accent, radius.md, motion.*
- Состояния и правила
- Основная и вторичная; наведение, фокус. Одна основная кнопка на экран
Карточка
- Статус
- Готов
- Токены
- color.surface, color.border, radius.lg, space.lg
- Состояния и правила
- Без тени в тёмной теме: глубину даёт цвет поверхности
Поле ввода
- Статус
- В работе
- Токены
- color.border, color.danger, radius.md
- Состояния и правила
- Фокус, ошибка с текстом, недоступно
Бейдж
- Статус
- Черновик
- Токены
- color.accent, font.size.small
- Состояния и правила
- Нужен токен радиуса-«пилюли»
Модальное окно
- Статус
- Не начато
- Токены
- —
- Состояния и правила
- Сначала описать фокус и закрытие с клавиатуры
Имена токенов для кода — такой же публичный интерфейс, как функции библиотеки. Semantic Versioning предписывает повышать мажорную версию при несовместимых изменениях API, минорную — при обратно совместимых добавлениях, патч — при исправлениях. Наш совет — переносить это на токены буквально: переименование или удаление токена — мажорная версия, новый токен — минорная, исправленное значение — патч. Перед удалением помечайте токен $deprecated с подсказкой, чем его заменить.
- Базовая палитра: нейтральные, фирменный, цвета ошибки и успеха
- 8–12 семантических цветов: фон, поверхность, граница, текст, текст второго плана, акцент, текст на акценте, ошибка
- Шкала отступов с шагом 4/8 — подробнее в уроке о визуальной иерархии
- 3–5 размеров шрифта и 2 начертания — из урока о типографике и цвете
- 2–3 радиуса, 2–3 длительности и одна-две кривые easing
- Кнопка, поле ввода и карточка со всеми состояниями
- Таблица компонентов со статусами и правило версий
Как проверено6 · пройдено1 · не запускалосьсверено 11 октября 2026 г.
- запущено, работает
tokens/tokens.json, tokens/tokens.dark.json
JSON.parse в Node.js 24.18; components ↔ hex сверены скриптом (расхождение ≤ 0,002)
- запущено, работает
build-tokens.mjs → tokens.css
node build-tokens.mjs: 40 токенов, 9 переопределений; негативный тест — несуществующая ссылка {base.color.neutral-1000} останавливает сборку; фрагмент tokens.css в статье взят из сгенерированного файла
- запущено, работает
components.css + index.html
Playwright 1.64 (из node_modules репозитория), channel msedge, Edge 155 headless: getComputedStyle в светлой/тёмной prefers-color-scheme, data-theme=light/dark, hover (accent-hover), ловушка наследования (переопределение базового токена на .card не меняет фон); скриншоты shot-light.png / shot-dark.png; 9/9 проверок
- запущено, работает
контраст семантических пар
формула относительной яркости WCAG в verify.mjs, все 9 пар ≥ 4,5:1 в обеих темах; числа таблицы — вывод скрипта
- запущено, работает
EN-версия кода (переведены комментарии, сообщения и тексты страницы)
тот же verify.mjs в отдельной папке dizajn-sistema-en: 9/9
- запущено, работает
решения упражнений 1–2 (.badge с radius.pill, .button--danger с on-danger)
копия проекта ds-ex: сборка 42 токена, контраст on-danger/danger ≥ 4,5:1 в обеих темах, computed: danger rgb(196,43,43)/rgb(255,155,148) при hover, badge radius 999px
- не запускалось
упражнение 3 (режим повышенной контрастности)
решение дано текстом без кода; значения не проверялись
«Запущено» — код выполнен во временном проекте или контейнере, вывод в статье взят из прогона. «Частично» — проверено с заглушками вместо внешних сервисов. «Не запускалось» — нужны устройство, аккаунт или ключ; шаги сверены с официальной документацией. Файлы проверочного проекта и логи запусков сохранены в архиве InterLAB.