-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
CSS Cascade Layers (@layer) for CKEditor 5 #20025
Description
While working on UI customization by introducing more granular CSS design tokens, I identified a few potential problems that we could address:
- bundle-order independence,
- stylesheet order → reliable token overrides,
- avoiding the need for !important.
Proposal: CSS Cascade Layers (@layer) for CKEditor 5
Problem
When CKEditor's CSS is bundled with integrator styles, the output order is unpredictable. An integrator's :root { --ck-radius-base: 5px } might appear before the editor's :root { --ck-radius-base: 2px } in the bundle - and the editor's value silently wins. Integrators resort to !important or high-specificity selectors to force their overrides.
Solution
Wrap CKEditor's CSS in a single ckeditor5 layer with internal sub-layers, and provide a dedicated ck-overrides layer for integrator customizations.
Layer architecture
@layer ckeditor5, ck-overrides;
@layer ckeditor5 {
@layer reset, tokens, ui, content;
}External API (what integrators see)
| Layer | Who uses it | Priority |
|---|---|---|
ckeditor5 |
Editor internals (don't touch) | Lower |
ck-overrides |
Integrator customizations | Higher |
Internal sub-layers (implementation detail)
| Sub-layer | What goes in | Priority within ckeditor5 |
|---|---|---|
ckeditor5.reset |
Element resets (box-sizing, margin, font) | Lowest |
ckeditor5.tokens |
All :root token definitions (foundation, semantic, component) |
|
ckeditor5.ui |
Component selectors (.ck-button, .ck-toolbar, etc.) |
|
ckeditor5.content |
Editable area styles (.ck-content typography, lists, tables) |
Highest (within editor) |
Integrator usage - simple
@layer ck-overrides {
:root {
--ck-radius-base: 5px;
--ck-spacing-sm: 5px;
--ck-toolbar-background-color: red;
--ck-balloon-panel-arrow-display: none;
--ck-dropdown-panel-uniform-border-radius: var(--ck-radius-base);
}
.ck.ck-toolbar {
padding: 0 10px;
}
}Integrator usage - don't want layers at all
/* Unlayered CSS beats all layers per spec. Still works. */
:root {
--ck-radius-base: 5px;
}What this solves
- Bundle ordering - layer priority is declared once upfront. Source order in the bundled file doesn't matter.
- No more
!important- the layer hierarchy guarantees priority. - Reliable token overrides - integrator tokens in
ck-overridesalways beat editor tokens inckeditor5.tokens. - CSS selector overrides -
.ck.ck-toolbar {}inck-overridesbeats the same selector inckeditor5.ui.
Why nested sub-layers
- For integrators: Simple - they only interact with
ck-overrides. One instruction, one layer. - For us: Full internal control - reset < tokens < ui < content priority. Adding new sub-layers (e.g.,
ckeditor5.pluginsfor feature styles,ckeditor5.themesfor dark mode) doesn't change the integrator API. - For advanced integrators: Sub-layers are documented but optional. Someone who needs to override only content styles without affecting UI can target
ckeditor5.contentdirectly. - Future-proof: Internal reorganization never breaks the external
ck-overridescontract.
Backward compatibility
- Integrators who don't use
@layerat all still work - unlayered CSS beats all layers per the CSS spec. - The
ck-overrideslayer is opt-in for integrators who want guaranteed priority within the layered system. - The three-tier design token system (#19910) maps naturally to the
tokens/ui/contentsub-layer boundaries.
Risks to investigate
- Integrators with existing unlayered CSS that was previously losing on specificity would now automatically win over all editor layers - could cause unintended visual changes.
- Requires coordination across all packages (ui, feature packages, theme).
- Browser support:
@layeris supported in all browsers since 2022 (Chrome 99+, Firefox 97+, Safari 15.4+). Verify this matches CKEditor's browser support matrix.
Prerequisite
The three-tier design token system (done in #19910) provides the architectural foundation. The token/semantic/component file organization already maps to the proposed sub-layer boundaries.
If you'd like to see this improvement implemented, add a 👍 reaction to this post.