---
title: "Styles API — CSS-классы редактора Blok"
description: "CSS-классы, которые Blok отдаёт наружу, чтобы собственный инструмент выглядел так же, как встроенные блоки."
source: https://blokeditor.com/ru/docs/styles-api/
lastmod: 2026-09-07
---

Фреймворк JavaScript

Редактирование Стили

На этой странице block

# Styles API: классы для ваших инструментов

Доступ к CSS-классам для стилизации пользовательских инструментов и элементов интерфейса, а также настройка макета и обрамления редактора через публичные CSS-переменные. Основной способ переопределить токены темы — `style.tokens` в конфигурации конструктора Blok: передайте ключи `--blok-*` со значениями, и Blok внедрит таблицу стилей для экземпляра, которая автоматически достаёт и до редактора, И до интерфейса, портированного в `document.body` (поповеры, подсказки, элементы верхнего слоя); некорректные ключи пропускаются с предупреждением, а таблица стилей удаляется при destroy. Внедрённые значения `style.tokens` статичны в рамках приложения — они применяются одинаково в светлой и тёмной темах и в режиме только для чтения, поэтому зависящим от состояния токенам вроде бокового отступа редактора (gutter) место в CSS; `style.tokens` игнорирует ключи `--blok-editor-gutter-*` с предупреждением. Но они не зафиксированы на момент создания: `editor.tokens.set(tokens)` переписывает внедрённую таблицу стилей на лету — именно это нужно переключателю светлой/тёмной темы на стороне хоста; без него смена токена означала бы пересоздание редактора или ручное написание глобальной таблицы стилей, нацеленной на области портала. `set()` принимает полный набор токенов (замена, а не слияние), повторяя семантику `style.tokens`, поэтому токены, отсутствующие в новой палитре, перестают действовать, а `{}` удаляет таблицу стилей; `editor.tokens.get()` возвращает то, что применено сейчас. API доступен синхронно сразу после создания (вызовы до `isReady` буферизуются и воспроизводятся), а адаптеры React/Vue/Angular управляют им реактивно — передайте `style={{ tokens }}` (React/Vue) или `[styleTokens]` (Angular), и изменения синхронизируются на месте без пересоздания редактора. Как альтернатива на чистом CSS: собственная палитра Blok объявлена с нулевой специфичностью через `:where()`, поэтому один обычный селектор вида `[data-blok-interface] { --blok-popover-bg: … }` выигрывает независимо от порядка таблиц стилей — но, так как поповеры портируются в `document.body`, эта глобальная таблица стилей должна дополнительно нацеливаться на `[data-blok-popover], [data-blok-top-layer]`, чтобы достать до них. `--blok-content-max-width` остаётся определяющим в обоих режимах ширины — `width='full'` лишь подменяет его запасное значение на `none`. В режиме редактирования Blok автоматически резервирует 56px бокового отступа под плавающие элементы управления блоком +/⠿, а на обёртке появляется `data-blok-readonly`, пока активен режим только для чтения. Обычный режим только для чтения СОХРАНЯЕТ этот отступ: в нём живёт элемент «скопировать ссылку», появляющийся при наведении на блок, а `readOnly.set()` переключает режимы на месте — схлопывание отступа сдвигало бы документ вбок при каждом переключении. Отступ автоматически схлопывается до 0 только там, где он действительно мёртвое пространство: в режиме только для чтения без элементов управления (`readOnly: { hideControls: true }`, обёртка получает `data-blok-controls-hidden`) и при `hideToolbar: true` в конфигурации конструктора — панель инструментов при наведении тогда не открывается, обёртка получает `data-blok-toolbar-hidden`, и место под отступ не резервируется. `--blok-editor-gutter-start` — это точка переопределения, а не обязательное заклинание: задайте ему любое значение (в том числе `0px`, чтобы убрать отступ), чтобы изменить значение по умолчанию. Контракт переопределения отступа гарантирован, а не случаен: Blok объявляет и значение по умолчанию, и оба схлопывания по состоянию с нулевой специфичностью через `:where()` (это закреплено контрактным юнит-тестом), поэтому объявление токенов отступа на стороне хоста с любой положительной специфичностью всегда выигрывает каскад. Объявляйте их на самом элементе-обёртке (например, `[data-blok-interface] { --blok-editor-gutter-start: 16px }`), а не только на предке: схлопывания controls-hidden и toolbar-hidden переобъявляют эти токены на обёртке, а пользовательские свойства разрешаются из ближайшего объявления, поэтому значение на уровне предка проиграет схлопыванию, а значение на уровне обёртки его переживёт. Горизонтальное положение колонки контента тоже настраивается на уровне API — через `style.contentAlign?: 'left' | 'center' | 'right'` (по умолчанию `'left'`) в конфигурации конструктора Blok. Blok также перекрашивает нативное выделение текста внутри редактора через `--blok-selection-inline` — переопределите этот токен, чтобы изменить цвет, или передайте `style.nativeSelection: true` (по умолчанию `false`), чтобы полностью отказаться от этого и вернуться к цветам выделения браузера/хоста (переопределение токена не может выразить общие CSS-ключевые слова вроде `revert`, поэтому для возврата нужен именно этот флаг). С включённым флагом обёртка получает `data-blok-native-selection`, правила `::selection` от Blok обходят редактор, а подсветка fake-background (показываемая, пока фокус удерживает поле ввода в меню) следует цвету `Highlight` из браузера; в поповерах цвет выделения от Blok сохраняется. Фоновые поверхности — тоже публичные токены: большинство светлых поверхностей и поверхностей при наведении следуют `--blok-bg-light`, карточки пустого состояния медиа используют `--blok-bg-secondary` (с границей `--blok-border-secondary`), а скелетоны загрузки изображений/файлов и плейсхолдеры загрузки — `--blok-bg-tertiary`, который по умолчанию равен `--blok-bg-light` и потому следует за темой; перекрасить поверхность скелетона — значит переопределить `--blok-bg-tertiary` напрямую, а не перегружать `--blok-bg-light`, утягивая за собой все остальные поверхности. Как и все цветовые токены палитры, токены поверхностей переобъявляются самим Blok на обёртке редактора с нулевой специфичностью, поэтому применяйте переопределения через `style.tokens` / `editor.tokens.set()` либо через CSS-селектор, попадающий в саму обёртку (`[data-blok-interface]`): объявление пользовательского свойства на контейнере-предке перекрывается собственным объявлением обёртки и молча ничего не делает (компоновочные хуки вроде `--blok-content-max-width`, а также токены списков, заголовков, embed, отступов блока и цвета плейсхолдера, наоборот, читаются с запасными значениями и никогда не объявляются самим Blok — поэтому они ДЕЙСТВИТЕЛЬНО наследуются от любого предка; токены бокового отступа и `--blok-search-input-placeholder` объявляются на обёртке, как палитра, поэтому им тоже нужно правило уровня обёртки). Учтите также, что внедряемые таблицы токенов нацелены на атрибуты области Blok глобально: если на странице несколько экземпляров редактора, таблица стилей `style.tokens` / `tokens.set()` каждого экземпляра применяется ко ВСЕМУ интерфейсу Blok на странице, а не только к своему экземпляру (каждая удаляется при уничтожении своего экземпляра; при конфликте наборов между экземплярами решает порядок таблиц стилей в `<head>`, а не давность применения, поэтому давайте всем экземплярам один общий набор вместо расчёта на порядок конфликта) — различия между экземплярами задавайте CSS-правилом на собственной обёртке каждого редактора (интерфейс поповеров, смонтированный в body, всегда следует общестраничным таблицам). Таблицы внедряются в начало `<head>`, поэтому правило таблицы стилей хоста с равной специфичностью — обычное `[data-blok-interface] { … }` — всё равно побеждает `style.tokens` для объявленных в нём токенов. Ритм блоков тоже публичен: `--blok-block-padding-top`, `--blok-block-padding-bottom` и `--blok-block-padding-inline` задают внутренние отступы обёртки любого блочного инструмента (параграф, заголовок, список, тоггл, цитата). Каждый инструмент сохраняет своё историческое значение как запасное — 7px/7px/2px у большинства блоков, 0.2em по вертикали у цитат — поэтому одно переопределение перенастраивает все блоки сразу; именно это нужно хосту в режиме только для чтения для плотной строчной вёрстки (раньше это было возможно только переопределением внутренностей `[data-blok-tool]`). Панель выноски — намеренное исключение: внутренний отступ её карточки задаётся `--blok-callout-padding-block` (по умолчанию 5px), а НЕ токенами ритма, поэтому уплотнение ритма не может схлопнуть карточку выноски на её текст — при этом эмодзи остаётся на первой строке текста, потому что его кнопка следует за `--blok-block-padding-top` вместе с дочерним текстом. Учтите, что нестандартные отступы слегка сдвигают производную геометрию — например, смещение стрелки заголовка-тоггла, которое следует за `--blok-block-padding-top`. Раскладка колонок публична точно так же: строка колонок — это `[data-blok-columns]`, а холдер каждой колонки — один из её непосредственных дочерних `[data-blok-element]` (строка в режиме только для чтения дополнительно несёт `data-blok-columns-static-gutter`, поскольку в опубликованных строках зазор задаёт контейнер, а не разделители `[data-blok-column-resizer]`, которые существуют только во время редактирования). `--blok-column-gutter` задаёт этот зазор (по умолчанию `min(2rem, 4vw)`), а `--blok-column-min-width` — насколько сильно можно сжать колонку (по умолчанию `0`, то есть колонку можно перетащить до полного схлопывания). Нижний предел соблюдают И раскладка, И перетаскивание разделителя — при нажатии указателя перетаскивание считывает вычисленное значение обратно, — поэтому его повышение останавливает ручку на этом пределе, а не сохраняет ширину, которую раскладка откажется отрисовывать. Вложенность блоков публична так же: блок, вложенный в другой (Tab на верхнем уровне), получает отступ `--blok-block-indent-step` на каждый уровень (по умолчанию `24px`), и это настоящий CSS, а не инлайновый стиль, поэтому обычное правило хоста меняет или убирает его без `!important`. Blok обнуляет шаг внутри каждого дочернего слота `[data-blok-nested-blocks]` — этот маркер рендерит для своих детей любой контейнерный инструмент, встроенный и сторонний, — поэтому блоки, которые контейнер уже расположил сам, не сдвигаются дополнительно по своей глубине; контейнеру, которому отступ нужен, достаточно объявить шаг обратно на своём слоте. Этот сброс держится на наследовании, а не на проверке в JS, именно затем, чтобы он действовал и для слота, созданного уже после вставки дочернего блока, — а именно так поступает портал адаптера фреймворка. Размер текста публичен и для блока, И для сценария через `style.fontSize` — это поддерживаемая альтернатива обращению к внутренним именам классов Blok. Каждый ключ пишет один публичный токен: `fontSize.paragraph` → `--blok-paragraph-font-size`, `fontSize.heading[1]` → `--blok-heading-1-font-size` (заголовки переиспользуют уже существующие токены заголовков, а не заводят параллельные), `fontSize.list.checklist` → `--blok-checklist-font-size`, и так же для обоих вариантов цитаты, выноски, кода, тоггла, двух плотностей таблицы (`compact` / `comfortable`), каждой подписи к медиа (изображение, видео, аудио, файл, embed) и трёх частей закладки (заголовок, описание, ссылка). Опущенные ключи сохраняют встроенный размер Blok, поэтому везде, где вы не подключились явно, редактор отображается ровно как раньше. Настройки размера на уровне инструмента по-прежнему главнее: инструмент параграфа с `styles.size` или список с `itemSize` пишет этот размер инлайновым стилем на блоке, и его не переопределить никаким токеном — поэтому сценарии, которыми вы хотите управлять через `style.fontSize`, не должны одновременно нести размер на уровне инструмента. Значения могут быть абсолютными или относительными (`px`, `rem`, `em`, `%`): каждый декоративный элемент рядом с текстом заданного размера — маркер списка, чекбокс, эмодзи выноски, стрелка тоггла — выводит собственные метрики из того же токена, поэтому остаётся оптически выровненным при любом масштабе без дополнительного CSS. Так как эти токены читаются с запасными значениями и никогда не объявляются самим Blok, редактор, который НЕ настраивает `style.fontSize`, принимает их и из обычного CSS-правила на любом предке, и из `style.tokens` / `editor.tokens.set()`. Сами ИМЕНА токенов поставляются константой — `import { BLOK_FONT_SIZE_TOKENS } from '@dodopizza/blok'` даёт карту ровно той же формы, что и конфигурация (`BLOK_FONT_SIZE_TOKENS.paragraph`, `BLOK_FONT_SIZE_TOKENS.heading[1]`, `BLOK_FONT_SIZE_TOKENS.bookmark.link`…), поэтому хосту, который задаёт типографику из CSS, никогда не приходится переписывать эти строки вручную, а переименование становится ошибкой компиляции, а не молчаливым no-op. Каналы дополняют друг друга: `style.fontSize` — это значение на момент создания, а `editor.tokens.set({ [BLOK_FONT_SIZE_TOKENS.paragraph]: '18px' })` переопределяет его на лету — таблица токенов темы внедряется сразу после таблицы fontSize при равной специфичности, поэтому она выигрывает. Именно этот канал нужен размеру, который должен меняться после монтирования (переключатель плотности, масштаба или доступности); сам `style.fontSize` читается один раз при создании. В отличие от `style.tokens`, внедряемая таблица fontSize ограничена своим редактором: обёртка несёт `data-blok-instance`, и селектор редактора в этой таблице привязан к нему, поэтому второй редактор на странице сохраняет встроенные размеры Blok (или собственную конфигурацию), а не наследует размеры первого. Общестраничным остаётся лишь интерфейс, смонтированный в body, — поповеры и подсказки отрисовываются вне поддерева любого редактора, поэтому при расхождении экземпляров эти правила следуют порядку в `<head>`. Стоит знать одно правило вложенности: выноска отображает свой текст как дочерний блок-параграф, поэтому текст выноски следует `fontSize.callout` и откатывается к `fontSize.paragraph`, когда этот ключ не задан — задав только `paragraph`, вы измените размер тела выносок вместе с основным текстом, а чтобы они различались, нужно задать `fontSize.callout` явно. Наконец, view-рендерер (`@bloklabs/core/view`) выдаёт семантический HTML, и его таблица стилей несёт только сценарии, основанные на классах: в выводе view работают параграф, заголовки, список, чек-лист, оба размера цитаты, выноска, код и тоггл, а размеры подписей, ячеек таблицы и закладок доступны только в редакторе.

[Редактировать на GitHub](https://github.com/JackUait/blok/blob/main/docs/src/components/api/api-data.ts)

## Свойства

| Свойство | Тип | Описание |
| --- | --- | --- |
| `block` | `string` | Стили базовой обёртки блока |
| `inlineToolButton` | `string` | Стили кнопки строчной панели |
| `inlineToolButtonActive` | `string` | Стили активной кнопки строчного инструмента |
| `input` | `string` | Стили элемента ввода |
| `loader` | `string` | Стили индикатора загрузки |
| `settingsButton` | `string` | Стили кнопки настроек |
| `settingsButtonActive` | `string` | Стили активной кнопки настроек |
| `settingsButtonFocused` | `string` | Стили кнопки настроек в фокусе |
| `settingsButtonFocusedAnimated` | `string` | Стили кнопки настроек в фокусе с анимацией нажатия |
| `button` | `string` | Стили кнопки общего назначения |

TypeScript

```
// Customize the editor from your host app via CSS custom properties —
// no need to target Blok's internal test IDs or data attributes.
// The hooks below are read with fallbacks and never declared by Blok, so
// they inherit from ANY ancestor — a plain container rule works:
.my-editor-container {
  /* Cap the content column at a custom width (default: 720px) */
  --blok-content-max-width: 650px;

  /* Extra start padding on list blocks (default: 0px) */
  --blok-list-padding-start: 18px;

  /* Checklists follow --blok-list-padding-start unless this is set —
     use it to indent checklists independently of other list styles */
  --blok-checklist-padding-start: 0px;

  /* Gap between a list marker/checkbox and its content (default: 0px) */
  --blok-list-gap: 6px;

  /* Indent applied per nesting level to blocks nested with Tab
     (default: 24px). Set it to 0px to switch nesting indentation off. */
  --blok-block-indent-step: 24px;

  /* Padding of every block tool wrapper. Fallbacks keep each tool's own
     default (7px/7px/2px for most blocks; quotes fall back to 0.2em
     vertical). Tighten all three for compact read-only inline
     rendering — no need to override [data-blok-tool]: */
  --blok-block-padding-top: 0;
  --blok-block-padding-bottom: 0.2em;
  --blok-block-padding-inline: 0;

  /* The callout card's own inset (default 5px) is deliberately separate
     from block rhythm — the rhythm override above leaves it alone: */
  --blok-callout-padding-block: 4px;

  /* Placeholder color of empty blocks (default: follows --blok-gray-text) */
  --blok-placeholder-color: rgba(112, 118, 132, 0.6);

  /* Heading typography (defaults mirror the built-in scale) */
  --blok-heading-1-font-size: 32px;
  --blok-heading-font-weight: 600;
  --blok-heading-margin-top: 16px;
  --blok-heading-margin-bottom: 16px;

  /* Space above embed blocks (default: 8px) */
  --blok-embed-margin-top: 16px;
}

// Container tools decline the nesting indent automatically: Blok zeroes the
// step inside every [data-blok-nested-blocks] child slot, so blocks your
// container positions itself are never also pushed by their depth. The indent
// is plain CSS, so opting it back IN inside your container is a declaration,
// not an !important fight:
.my-container-tool [data-blok-nested-blocks] {
  --blok-block-indent-step: 24px;
}

// Primary way to override theme tokens: style.tokens in the constructor
// config. Blok injects a per-instance stylesheet that reaches the editor
// AND UI portaled to document.body (popovers, tooltips, top-layer
// elements) automatically — no manual selector targeting needed.
new Blok({
  style: {
    tokens: {
      '--blok-selection': 'rgba(35, 131, 226, 0.28)',
      '--blok-popover-bg': '#1f1f1f',

      // Surface backgrounds. Most hover/light surfaces follow
      // --blok-bg-light; media empty-state cards use --blok-bg-secondary
      // with --blok-border-secondary; image/file loading skeletons and
      // upload placeholders use --blok-bg-tertiary, which defaults to
      // --blok-bg-light. Override the specific token you mean — no need
      // to overload --blok-bg-light to reach the skeleton surface.
      // NOTE: palette-backed color tokens like these are re-declared by
      // Blok on the editor wrapper itself, so set them here (or with a
      // CSS selector matching the wrapper, as below) — a declaration on
      // an ancestor container is shadowed and does nothing.
      '--blok-bg-light': '#eff2f5',
      '--blok-bg-secondary': '#f7f8fa',
      '--blok-border-secondary': 'rgba(55, 53, 47, 0.09)',
      '--blok-bg-tertiary': '#f0f0f0',
    },
  },
});

// CSS-only alternative: a plain host selector works too — Blok's palette
// is declared at zero specificity via :where(), so this always wins.
// Popovers/menus portal to document.body, so target them explicitly too.
[data-blok-interface],
[data-blok-popover],
[data-blok-top-layer] {
  --blok-popover-bg: #1a1a1a;

  /* Placeholder color of popover search inputs — wrapper-declared by Blok
     (like the palette) and consumed inside body-mounted popovers, so set
     it here or via style.tokens, never on an ancestor container: */
  --blok-search-input-placeholder: rgba(112, 118, 132, 0.8);
}

// Blok reserves 56px of start gutter automatically in edit mode for the
// floating +/⠿ block controls, collapsing to 0 only when the gutter is
// dead space: chromeless read-only (data-blok-controls-hidden) or
// hideToolbar (data-blok-toolbar-hidden). Plain read-only keeps the
// gutter so in-place mode flips never shift the layout.
// The gutter tokens are declared on the wrapper by Blok (default + state
// collapses), so overrides MUST target the wrapper itself — an ancestor
// container rule is shadowed and does nothing. Set any value, including
// 0px to remove the gutter, or redeclare it to opt back into the
// reserved space while controls are hidden:
.my-editor-container [data-blok-interface] {
  --blok-editor-gutter-start: 56px;
  --blok-editor-gutter-end: 16px;
}

// Center the content column instead of left-aligning it (default: 'left')
const editor = new Blok({
  holder: 'editor',
  style: { contentAlign: 'center' },
});

// Per-block, per-scenario type scale — every text-bearing block (and every
// scenario inside it: a caption, a density mode, a size variant) is settable
// from the config. This is the supported way to resize block text; there is
// no need to target Blok's internal class names. Each entry writes that
// block's --blok-*-font-size token. Omitted keys keep Blok's built-in size,
// and values may be absolute or relative (px / rem / em / %) — bullets,
// checkboxes, the callout emoji and the toggle arrow derive their metrics
// from the same token, so they stay aligned at any scale.
new Blok({
  holder: 'editor',
  style: {
    fontSize: {
      paragraph: '17px',
      // Headings reuse the existing --blok-heading-N-font-size tokens
      heading: { 1: '2.25rem', 2: '1.75rem', 3: '1.375rem' },
      list: { item: '17px', checklist: '17px' },
      quote: { default: '17px', large: '1.4em' },
      // Callout text is a child paragraph block: it follows this key, and
      // falls back to fontSize.paragraph when this key is omitted. Set it
      // explicitly to make callout text differ from body text.
      callout: '17px',
      code: '13px',
      toggle: '17px',
      table: { compact: '14px', comfortable: '17px' },
      image: { caption: '13px' },
      video: { caption: '13px' },
      audio: { caption: '13px' },
      file: { caption: '13px' },
      embed: { caption: '13px' },
      bookmark: { title: '15px', description: '13px', link: '13px' },
    },
  },
});

// Sizes that must CHANGE after mount (density / zoom / accessibility toggle)
// go through the token channel instead — style.fontSize is read once at
// construction and its stylesheet outranks tokens.set() for the scenarios it
// declares. So leave those scenarios out of style.fontSize entirely and
// size them from the token channel only:
editor.tokens.set({
  '--blok-paragraph-font-size': compact ? '15px' : '18px',
  '--blok-list-font-size': compact ? '15px' : '18px',
});

// Unconfigured scenarios also accept a plain CSS rule from any ancestor —
// Blok reads these tokens with fallbacks and never declares them itself:
.my-editor-container {
  --blok-quote-large-font-size: 24px;
}

// NOTE: the sheet Blok injects for style.fontSize is page-global, like
// style.tokens — it retypes EVERY editor on the page, including instances
// that set no fontSize of their own. Scope per-instance differences with a
// rule on that editor's own wrapper:
#editor-b [data-blok-interface] {
  --blok-paragraph-font-size: 15px;
}

// Flip theme tokens at runtime — e.g. from a host light/dark toggle.
// set() replaces the whole set, so tokens dropped from the new palette
// stop applying. Available immediately; calls before isReady are buffered.
editor.tokens.set({
  '--blok-popover-bg': isDark ? '#1f1f1f' : '#ffffff',
  '--blok-text-primary': isDark ? '#e6e6e6' : '#1a1a1a',
});
editor.tokens.get(); // -> currently applied tokens

// In React/Vue the same channel is a reactive prop (Angular: [styleTokens])
<BlokEditor style={{ tokens: isDark ? darkTokens : lightTokens }} />

// Opt out of Blok's ::selection repaint and use the native/host-defined
// selection colors instead (recoloring is possible via
// --blok-selection-inline; reverting to the UA default needs this flag)
new Blok({
  holder: 'editor',
  style: { nativeSelection: true },
});

// Access CSS class names for styling custom tools
const styles = editor.styles;

// Use class names in your custom tool
class MyCustomTool {
  constructor({ api }) {
    this.api = api;
  }

  render() {
    const wrapper = document.createElement('div');
    wrapper.className = this.api.styles.block;

    const input = document.createElement('input');
    input.className = this.api.styles.input;

    const button = document.createElement('button');
    button.className = this.api.styles.button;
    button.textContent = 'Click me';

    wrapper.appendChild(input);
    wrapper.appendChild(button);

    return wrapper;
  }
}

// Available class names:
// - api.styles.block              // Base block wrapper
// - api.styles.inlineToolButton   // Inline toolbar button
// - api.styles.inlineToolButtonActive  // Active inline tool
// - api.styles.input              // Input elements
// - api.styles.loader             // Loading spinner
// - api.styles.settingsButton     // Settings button
// - api.styles.settingsButtonActive   // Active settings
// - api.styles.settingsButtonFocused  // Focused settings
// - api.styles.settingsButtonFocusedAnimated  // Focused settings with click animation
// - api.styles.button             // General button
```
