#20234·ckeditor5

[RFC] Design token system and refreshed default theme in v49.0.0

Author: pszczesniakCreated Sep 14, 2026Updated Sep 15, 2026
Labelstype:improvementpackage:theme-larkdomain:ui/uxbc:majordomain:dx

Summary

We are going to refresh CKEditor 5's default look, and to do it well we are reorganizing the styles around a structured, tiered design-token system. Two things will change:

  • A refreshed default theme. The editor will look more modern out of the box. The previous look will remain available as an opt-in legacy theme.
  • A new token architecture. The --ck-* custom properties will be arranged into three tiers - foundation, semantic, component - so the theme can be customized by intent and evolved without regressions.

This will be a breaking change, mostly visual, released in the next major version (v49). Every integration that uses the default theme will get the new look. Integrators who customized the theme will keep their customizations, but the defaults underneath them will change, so some re-checking may be needed. Old token names will keep working - code that reads them still resolves. Anyone who relies on the old default appearance, or overrides legacy token names to re-skin the editor's internals, needs to know how to get the previous look back.

Why we are doing this

We want to modernize the editor's default appearance. The current theme makes that hard: the styles are a flat, loosely-structured set of --ck-* custom properties, so changing the look safely - and letting integrators customize it predictably - is difficult.

So before touching the look, we revised the architecture. The rest of this document covers the problems in detail, the token model we are moving to, the refreshed theme itself, and how existing integrations upgrade.

Problems

1. Names describe size, not intent

--ck-spacing-tiny tells you a value, not what it is for. An integrator who wants "more compact controls" has to discover which of several raw tokens each control happens to read. There is no way to express intent ("compact padding") independently of the pixel value behind it.

2. There is no tier structure

A semantic token can hold an inline literal. A component can reach past the semantic layer straight into a foundation value. Without a "reference the tier below" rule, the same concept is expressed inconsistently across the codebase, and there is no single source of truth for the scale. Foundation-level tokens are also redefined inside component scopes, so a global value can mean one thing across the editor and something else inside a particular component, with every nested element silently inheriting the local override - which makes the effect of a change hard to predict.

3. The default look cannot be refreshed safely

The same visual value lives in many files - sometimes as a token, sometimes as a hardcoded literal - with no single place that owns it. Refreshing the look means finding and editing every occurrence by hand, and because nothing ties them together, there is no way to confirm you changed only what you meant. A tweak aimed at buttons can quietly shift a comment box or a toolbar, and the regression surfaces only later.

4. Customization is unpredictable

Integrators override tokens whose names and locations are not guaranteed, and there is no documented contract for what is safe to target. Renames or moves during any refactor can silently break themes.

5. Upgrades can break custom styling

When a token is missing, the only option is to style the editor by targeting its class names and DOM structure directly. Those are internal details, not a guaranteed styling surface, so a markup or specificity change in a later version can silently stop a custom rule from applying - and the breakage surfaces only after upgrading.

The new token architecture

Styles are built on a three-tier token model with a strict "reference the tier below" rule, and the refreshed default theme is expressed entirely through it.

  • Foundation - the raw scale: --ck-spacing-{2xs..xl}, --ck-radius-*, --ck-font-size-*, --ck-color-base-*, --ck-shadow-{md,lg,xl}. One place, centralized.
  • Semantic - intent-named tokens that reference foundation: --ck-border-radius-control, --ck-color-surface-canvas, --ck-color-interactive-hover-surface, and the density scale below.
  • Component - per-component tokens that reference the semantic layer and name the purpose: --ck-toolbar-padding, --ck-input-border-radius.

A component never reaches past the semantic layer into foundation, and a semantic token never inlines a literal.

For integrators, this turns the tokens into a supported, documented contract: a stable surface to customize against, instead of overriding whatever selector happens to work. That is the answer to problems 4 and 5 above.

Why the tiers: a worked example

Take one foundation value, the medium radius:

css
--ck-radius-md: 4px;

Change it and the radius shifts across the whole editor, everywhere that token is used. That is rarely what you want. Usually you mean to reshape one kind of thing. That is what the semantic tier is for. Controls read a semantic token that points at the foundation value:

css
--ck-border-radius-control: var(--ck-radius-md);

This drives the radius of every control (buttons, inputs, textareas, and so on). Overriding --ck-border-radius-control reshapes only those controls and leaves --ck-radius-md, and everything else built on it, untouched.

The component tier narrows it once more. Each component points at the semantic token:

css
--ck-button-border-radius: var(--ck-border-radius-control);

Overriding --ck-button-border-radius changes the radius on buttons only.

The three tiers give three scopes for the same change: foundation reshapes everything, semantic reshapes a whole category, component reshapes one component. You override at the tier that matches your intent.

Spacing by intent: a density scale

Padding and gap are chosen from a small named scale, separate from purpose. The component token names the purpose. The scale names the comfort level:

level value padding token gap token
tight 2px --ck-spacing-padding-tight --ck-spacing-gap-tight
compact 4px --ck-spacing-padding-compact --ck-spacing-gap-compact
default 8px --ck-spacing-padding --ck-spacing-gap
comfortable 12px --ck-spacing-padding-comfortable --ck-spacing-gap-comfortable
spacious 16px --ck-spacing-padding-spacious --ck-spacing-gap-spacious
css
/* the component token picks a density from the semantic scale and names the purpose */
--ck-toolbar-padding: 0 var(--ck-spacing-padding);

A vocabulary that separates appearance from spacing

Two word-pairs are kept distinct rather than merged:

  • surface / overlay - how a raised sheet looks and the backdrop behind a modal (color, radius, elevation).
  • panel / dialog - spacing purposes for those containers.

Conflating them would lose a real distinction, so they stay separate.

The refreshed default theme

With the architecture in place, the visual refresh is a set of value changes on top of it - colors, spacing, radius, elevation and font - for a more modern default look. The reorganization itself keeps every resolved value identical. The new look is applied separately, as its own reviewable set of changes, which lets us refresh it with confidence.

The refreshed theme will become the default. The previous look will remain available as an opt-in legacy theme - a single stylesheet you load - so integrations that prefer the old appearance can keep it with a one-line change. The refresh will also affect integrators who customized the theme - their overrides still apply, but the defaults beneath them will change, so a customization tuned against the old look is worth re-checking.

One change reaches beyond the editor itself: a few .ck-content styles will change color (comment and suggestion markers, block quote, code block, horizontal line), so existing documents will render slightly differently. Because this affects published content, it is called out separately and will be shipped with rollback snippets to restore the previous colors.

Backward compatibility

For most integrations this will be a small change - often nothing. The editor's markup will not be touched: CSS class names and DOM structure will stay the same, so any selector you already target will keep matching, and old token names will keep resolving. Only the default values behind those hooks will change, so most customizations will keep working. The ones worth a second look are those tuned against the old values.

How the old names will keep working

Nothing that references an old token name will break. Three layers carry the compatibility:

  1. Legacy name aliases. Old foundation names map to new ones - --ck-spacing-small: var(--ck-spacing-sm) - so integrator code that reads an old name keeps resolving, now to the new-theme value.
  2. Per-component bridges. Where a component historically read var(--ck-legacy, var(--ck-new)), the new default reads the new token directly, and the legacy override hook moves to the opt-in legacy theme.
  3. The legacy theme (opt-in). Restores the previous values and override hooks wholesale.

Reading a legacy name and overriding one behave differently, and this is the part most worth understanding:

  • Reading an old name in your own CSS (padding: var(--ck-spacing-tiny)) keeps working and picks up the new-theme value automatically.
  • Overriding an old name to re-skin the editor's internals (:root { --ck-spacing-tiny: 0 }) is no longer guaranteed in the new default, because the editor's components now read the new names directly. To change the editor's built-in look, override the new tokens, or load the legacy theme for the previous look wholesale.

Deprecation and removal

The legacy name aliases are a compatibility shim for the transition. As of v49 the old token names are deprecated: they will keep resolving so existing integrations keep working, but new code should use the new names. They will be removed in a future major release. The exact release will be confirmed against the roadmap, with a generous runway so integrators have time to migrate. A migration guide will accompany the change, along with tooling to flag old token names in a project.

How this will affect you

  • If you used the default theme and did not customize it - decide whether you want the new look (default) or the legacy one by applying the rollback snippet.
  • If you have a custom theme that overrides --ck-* tokens - your overrides will still apply. Review any place you set a legacy name expecting it to re-skin editor internals (see reading vs. overriding above), and switch those to the new tokens or the legacy theme.
  • If you customize with plain CSS (targeting .ck-* selectors and properties directly, not tokens) - your rules will still match. The defaults around your overrides will come from the new theme, so a customization tuned against the old look may need re-checking. To start from the previous baseline, load the legacy theme first and layer your CSS on top.
  • If you render published content (.ck-content) - check the content-style color changes and apply the rollback snippets if you need the previous colors.