# Code style

[CKEditor 5 development environment](development-environment.md) has ESLint enabled on CI. This means that code style issues are detected automatically. Additionally, `.editorconfig` files are present in every repository to automatically adjust your IDE settings (if it is configured to read them).

Here is a quick summary of these rules.

<a id="general">

## General

**LF for line endings**. Never use CRLF.

The recommended maximum **line length is 120 characters**. It cannot exceed 140 characters.

<a id="whitespace">

## Whitespace

**No trailing spaces**. Empty lines should not contain any spaces.

Whitespace **inside parenthesis** and **before and after operators**:

```js
function foo( a, b, c, d, e ) {
	if ( a > b ) {
		c = ( d + e ) * 2;
	}
}

foo( bar() );
```

No whitespace for an **empty parenthesis**:

```js
const a = () => {
	// Statements.
	// ...
};

a();
```

No whitespace **before colon and semicolon**:

```js
let a, b;

a( 1, 2, 3 );

for ( const i = 0; i < 100; i++ ) {
	// Statements.
	// ...
}
```

<a id="indentation">

## Indentation

Indentation with **tab**, for both code and comments. Never use spaces.

If you want to have the code readable, set the **tab** to **4 spaces** in your IDE.

```js
class Bar {
	a() {
		while ( b in a ) {
			if ( b == c ) {
				// Statements.
				// ...
			}
		}
	}
}
```

**Multiple lines condition**. Use **one tab** for each line:

```js
if (
	some != really.complex &&
	condition || with &&
	( multiple == lines )
) {
	// Statements.
	// ...
}

while (
	some != really.complex &&
	condition || with &&
	( multiple == lines )
) {
	// Statements.
	// ...
}
```

> **Note**
>
> We do our best to avoid complex conditions. As a rule of thumb, we first recommend finding a way to move the complexity out of the condition, for example, to a separate function with early returns for each “sentence” in such a condition.
>
> However, overdoing things is not good as well and sometimes such a condition can be perfectly readable (which is the ultimate goal here).

<a id="braces">

## Braces

Braces **start at the same line** as the head statement and end aligned with it:

```js
function a() {
	// Statements.
	// ...
}

if ( a ) {
	// Statements.
	// ...
} else if ( b ) {
	// Statements.
	// ...
} else {
	// Statements.
	// ...
}

try {
	// Statements.
	// ...
} catch ( e ) {
	// Statements.
	// ...
}
```

<a id="blank-lines">

## Blank lines

The code should read like a book, so put blank lines between “paragraphs” of code. This is an open and contextual rule, but some recommendations would be to separate the following sections:

* variable, class and function declarations,
* `if()`, `for()` and similar blocks,
* steps of an algorithm,
* `return` statements,
* comment sections (comments should be preceded with a blank line, but if they are the “paragraph” themselves, they should also be followed with one),
* etc.

Example:

```js
class Foo extends Plugin {
	constructor( editor ) {
		super( editor );

		/**
		* Some documentation...
		*/
		this.foo = new Foo();

		/**
		* Some documentation...
		*/
		this.isBar = false;
	}

	method( bar ) {
		const editor = this.editor;
		const selection = editor.model.document.selection;

		for ( const range of selection.getRanges() ) {
			const position = range.start;

			if ( !position ) {
				return false;
			}

			// At this stage this and this need to happen.
			// We considered doing this differently, but it has its shortcomings.
			// Refer to the tests and issue #3456 to learn more.

			const result = editor.model.checkSomething( position );

			if ( result ) {
				return true;
			}
		}

		return true;
	}

	performAlgorithm() {
		// 1. Do things A and B.
		this.a();
		this.b();

		// 2. Check C.
		if ( c() ) {
			d();
		}

		// 3. Check whether we are fine.
		const areWeFine = 1 || 2 || 3;

		this.check( areWeFine );

		// 4. Finalize.
		this.finalize( areWeFine );

		return areWeFine;
	}
}
```

<a id="multi-line-statements-and-calls">

## Multi-line statements and calls

Whenever there is a multi-line function call:

* Put the first parameter in a new line.
* Put every parameter in a separate line indented by one tab.
* Put the last closing parenthesis in a new line, at the same indentation level as the beginning of the call.

Examples:

```js
const myObj = new MyClass(
	'Some long parameters',
	'To make this',
	'Multi line'
);

fooBar(
	() => {
		// Statements.
		// ...
	}
);

fooBar(
	new MyClass(
		'Some long parameters',
		'To make this',
		'Multi line'
	)
);

fooBar(
	'A very long string',
	() => {
		// Some kind of a callback.
		// ...
	},
	5,
	new MyClass(
		'It looks well',
		paramA,
		paramB,
		new ShortClass( 2, 3, 5 ),
		'Even when nested'
	)
);
```

> **Note**
>
> These examples are just showcasing how to structure such function calls. However, it is best to avoid them.
>
> It is generally recommended to avoid having functions that accept more than 3 arguments. Instead, it is better to wrap them in an object so all parameters can be named.
>
> It is also recommended to split such long statements into multiple shorter ones, for example, by extracting some longer parameters to separate variables.

<a id="strings">

## Strings

Use **single quotes**:

```js
const a = 'I\'m an example for quotes';
```

Long strings can be **concatenated with plus** (`+`):

```js
const html =
	'Line 1\n' +
	'Line 2\n' +
	'Line 3';
```

or you can use template strings. The second and third line will be indented in this case:

```js
const html =
	`Line 1
	Line 2
	Line 3`;
```

Strings of HTML should use indentation for readability:

```js
const html =
	`<p>
		<span>${ a }</span>
	</p>`;
```

<a id="comments">

## Comments

* Comments are always **preceded by a blank line**.
* Comments start with a **capital first letter** and require a period at the end (since they are sentences).
* There must be a **single space at the start** of the text, right after the comment token.

**Block comments** (`/** ... */`) are used for **documentation only**. Asterisks are aligned with space:

```js
/**
 * Documentation for the following method.
 *
 * @returns {Object} Something.
 */
someMethod() {
	// Statements.
	// ...
}
```

All **other comments** use **line comments** (`//`):

```js
// A comment about the following statement.
foo();

// Multiple line comments
// go through several
// line comments as well.
```

**Comments related to tickets or issues** should not describe the whole issue fully. A short description should be used instead, together with the ticket number in parenthesis:

```js
// Do this otherwise because of a Safari bug. (#123)
foo();
```

<a id="linting">

## Linting

CKEditor 5 development environment uses [ESLint](https://eslint.org), including its [native CSS linting support](https://eslint.org/blog/2025/02/eslint-css-support/).

A couple of useful links:

* [Disabling ESLint with inline comments](https://eslint.org/docs/latest/use/configure/).
* [CKEditor 5 ESLint preset](https://github.com/ckeditor/ckeditor5-linters-config/blob/master/packages/eslint-config-ckeditor5/eslint.config.mjs) (npm: [`eslint-config-ckeditor5`](https://www.npmjs.com/package/eslint-config-ckeditor5)).

> **Note**
>
> Avoid using automatic code formatters on existing code. It is fine to automatically format code that you are editing, but you should not be changing the formatting of the code that is already written to not pollute your PRs. You should also not rely solely on automatic corrections.

<a id="visibility-levels">

## Visibility levels

Each class property (including methods, symbols, getters, or setters) can be public, protected, or private. The default visibility is public, so you should not document that a property is public – there is no need to do this.

Additional rules apply to private properties:

* The names of private and protected properties that are exposed in a class prototype (or in any other way) should be prefixed with an underscore.
* When documenting a private variable that is not added to a class prototype (or exposed in any other way), `//` comments should be used and using `@private` is not necessary.
* A symbol property (like `this[ Symbol( 'symbolName' ) ]`) should be documented as `@property {Type} _symbolName`.

Example:

```js
class Foo {
	/**
	* The constructor (public, as its visibility isn't defined).
	*/
	constructor() {
		/**
		* Public property.
		*/
		this.foo = 1;

		/**
		* Protected property.
		*
		* @protected
		*/
		this._bar = 1;

		/**
		* @private
		* @property {Number} _bom
		*/
		this[ Symbol( 'bom' ) ] = 1;
	}

	/**
	* @private
	*/
	_somePrivateMethod() {}
}

// Some private helper.
//
// @returns {Number}
function doSomething() {
	return 1;
}
```

<a id="accessibility">

### Accessibility

The table below shows the accessibility of properties:

|              | Class | Package | Subclass | World |
| ------------ | ----- | ------- | -------- | ----- |
| `@public`    | yes   | yes     | yes      | yes   |
| `@protected` | yes   | yes     | yes      | no    |
| `@private`   | yes   | no      | no       | no    |

(yes – accessible, no – not accessible)

For instance, a protected property is accessible from its own class in which it was defined, from its whole package, and from its subclasses (even if they are not in the same package).

> **Note**
>
> Protected properties and methods are often used for testability. Since tests are located in the same package as the code, they can access these properties.

<a id="getters">

## Getters

You can use ES6 getters to simplify class API:

```js
class Position {
	// More methods.
	// ...

	get offset() {
		return this.path[ this.path.length - 1 ];
	}
}
```

A getter should feel like a natural property. There are several recommendations to follow when creating getters:

* They should be fast.
* They should not throw.
* They should not change the object state.
* They should not return new instances of an object every time (so `foo.bar == foo.bar` is true). It is OK to create a new instance for the first call and cache it if it is possible.

<a id="order-within-class-definition">

## Order within class definition

Within the class definition, order the methods and properties as follows:

1. Constructor.
2. Getters and setters.
3. Iterators.
4. Public instance methods.
5. Public static methods.
6. Protected instance methods.
7. Protected static methods.
8. Private instance methods.
9. Private static methods.

The order within each group is left for the implementer.

<a id="tests">

## Tests

There are some special rules and tips for tests.

<a id="test-organization">

### Test organization

* Always use an outer `describe()` in a test file. Do not allow any globals, especially hooks (`beforeEach()`, `afterEach()`, etc.) outside the outermost `describe()`.

* The outermost `describe()` calls should create meaningful groups, so when all tests are run together a failing TC can be identified within the code base. For example:

  ```js
  describe( 'Editor', () => {
  	describe( 'constructor()', () => {
  		it( /* ... */ );
  	} );

  	// More test cases.
  	// ...
  } );
  ```

  Using titles like `utils` is not fine as there are multiple utilities in the entire project. `Table utils` would be better.

* Test descriptions (`it()`) should be written like documentation (what you do and what should happen), for example, _“the foo dialog closes when the X button is clicked”_. Also, _“…case 1”_, _“…case 2”_ in test descriptions are not helpful.

* Avoid test descriptions like _“does not crash when two ranges get merged.”_ Instead, explain what is actually expected to happen. For instance: _“leaves 1 range when two ranges get merged.”_

* Most often, using words like “correctly,” “works fine” is a code smell. Think about the requirements – when writing them you do not say that feature X should “work fine.” You document how it should work.

* Ideally, it should be possible to recreate an algorithm just by reading the test descriptions.

* Avoid covering multiple cases under one `it()`. It is OK to have multiple assertions in one test, but not to test, for example, how method `foo()` works when it is called with 1, then with 2, then 3, etc. There should be a separate test for each case.

* Every test should clean up after itself, including destroying all editors and removing all elements that have been added.

<a id="test-implementation">

### Test implementation

Tests run on [Vitest](https://vitest.dev/). See the [Testing environment](testing-environment.md) guide to learn how to execute them.

* Import every test API you use explicitly from `vitest`. Test globals are not enabled:

  ```js
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  ```

* Avoid using real timeouts. Use [fake timers](https://vitest.dev/api/vi.html#fake-timers) instead **when possible**. Timeouts make tests really slow.

  Fake timers are not restored automatically, so switch back to the real ones in the cleanup hook:

  ```js
  beforeEach( () => {
  	vi.useFakeTimers();
  } );

  afterEach( () => {
  	vi.useRealTimers();
  } );

  it( 'calls the callback after 100 ms', () => {
  	const callbackSpy = vi.fn().mockName( 'callback' );

  	scheduleCallback( callbackSpy );
  	vi.advanceTimersByTime( 100 );

  	expect( callbackSpy ).toHaveBeenCalledOnce();
  } );
  ```

* However, do not over-optimize (especially since performance is not a priority in tests). In most cases, it is completely fine (and hence recommended) to create a separate editor for every `it()`.

* We aim at having 100% coverage of _all distinctive scenarios_. Covering 100% branches in the code is not the goal here – it is a by-product of covering real scenarios.

  Think about this: when you fix a bug by adding a parameter to an existing function call, you do not affect code coverage (that line was called anyway). However, you had a bug, meaning that your test suite did not cover it. Therefore, a test must be created for that code change.

* Assert with the [Vitest `expect()` matchers](https://vitest.dev/api/expect.html). Use `toBe()` to compare primitives and object references, and `toEqual()` to compare structures by value:

  ```js
  expect( editor.getData() ).toBe( '<p>foo</p>' );
  expect( command.value ).toEqual( { type: 'bulleted' } );
  ```

  Vitest still exposes the Chai-style chained assertions used before the migration, but do not write new ones. It should be `expect( x ).toBe( y )`. **NOT**: ~~`expect( x ).to.equal( y )`~~.

* The testing environment also registers a few [custom matchers](testing-environment.md#custom-vitest-matchers), such as `toEqualMarkup()`. Prefer them over plain string comparisons when asserting markup, as they produce far more readable diffs.

* When using spies, pay attention to the readability of assertions and failure messages.

  * [`vi.spyOn()`](https://vitest.dev/api/vi.html#vi-spyon) names the spy after the method it replaces, but a bare [`vi.fn()`](https://vitest.dev/api/vi.html#vi-fn) does not. Name standalone spies with `mockName()`:

    ```js
    const myMethodSpy = vi.spyOn( obj, 'myMethod' );
    const someCallbackSpy = vi.fn().mockName( 'someCallback' );
    ```

  * The name is then used in the failure message:

    ```js
    expect( myMethodSpy ).toHaveBeenCalledOnce();
    // expected "myMethod" to be called once, but got 2 times
    ```

* Do not clean up spies and stubs by hand. The shared Vitest configuration enables [`restoreMocks`](https://vitest.dev/config/restoremocks), [`unstubGlobals`](https://vitest.dev/config/unstubglobals), and [`unstubEnvs`](https://vitest.dev/config/unstubenvs), so every `vi.spyOn()`, `vi.fn()`, `vi.stubGlobal()`, and `vi.stubEnv()` is restored before each test. Therefore:

  * Do not call `vi.restoreAllMocks()`, `vi.unstubAllGlobals()`, or `vi.unstubAllEnvs()` in cleanup hooks. The only reason to restore something manually is when a mock set up in `beforeEach()` must be dropped for a single test, or when the teardown itself (`editor.destroy()`, for example) has to run against the real implementation.
  * Create stubs in `beforeEach()` or in the test itself, never in `beforeAll()`. A stub created in `beforeAll()` would be silently removed before the second test of the suite runs.

<a id="naming">

## Naming

<a id="javascript-code-names">

### JavaScript code names

Variables, functions, namespaces, parameters and all undocumented cases must be named in [lowerCamelCase](https://en.wikipedia.org/wiki/CamelCase):

```js
let a;
let myLongNamedVariable = true;

function foo() {}

function longNamedFunction( example, longNamedParameter ) {}
```

Classes must be named in [UpperCamelCase](https://en.wikipedia.org/wiki/CamelCase):

```js
class MyClass() {}

const a = new MyClass();
```

Mixins must be named in [UpperCamelCase](https://en.wikipedia.org/wiki/CamelCase), post-fixed with “Mixin”:

```js
const SomeMixin = {
	method1: /* ... */,
	method2: /* ... */
};
```

Global namespacing variables must be named in [ALLCAPS](https://en.wikipedia.org/wiki/All_caps):

```js
const CKEDITOR_TRANSLATIONS = {};
```

<a id="private-properties-and-methods">

#### Private properties and methods

Private properties and methods are **prefixed with an underscore**:

```js
CKEDITOR._counter;
something._doInternalTask();
```

<a id="methods-and-functions">

#### Methods and functions

Methods and functions are **almost always** verbs or actions:

```js
// Good
execute();
this.getNextNumber()

// Bad
this.editable();
this.status();
```

<a id="properties-and-variables">

#### Properties and variables

Properties and variables are **almost always** nouns:

```js
const editor = this.editor;
this.name;
this.editable;
```

**Boolean properties and variables** are **always** prefixed by an auxiliary verb:

```js
this.isDirty;
this.hasChildren;
this.canObserve;
this.mustRefresh;
```

<a id="buttons-commands-and-plugins">

### Buttons, Commands and Plugins

<a id="buttons">

#### Buttons

All buttons should follow the **verb + noun** or the **noun** convention. Examples:

* The **verb + noun** convention:

  * `insertTable`
  * `selectAll`
  * `uploadImage`

* The **noun** convention:

  * `bold`
  * `mediaEmbed`
  * `restrictedEditing`

<a id="commands">

#### Commands

As for commands, it is trickier. There are more combinations of their names possible than there are for buttons. Examples:

* The **feature-related** convention:

  * **noun-based** case:

    * `codeBlock`
    * `fontColor`
    * `shiftEnter`

  * **verb-based** case:

    * `indent`
    * `removeFormat`
    * `selectAll`

* The **feature + sub-feature** convention:

  * `imageStyle`
  * `imageTextAlternative`
  * `tableAlignment`

For commands, the **noun + verb** (or the **feature + action**) naming conventions **should not be used**, because it does not sound natural (_what do_ vs. _do what_). In most cases the proper name should start with the **action** followed by the **feature** name:

* `checkTodoList`
* `insertTable`
* `uploadImage`

<a id="plugins">

#### Plugins

Plugins should follow the **feature** or the **feature + sub-feature** convention. Examples:

* The **feature** convention:

  * `Bold`
  * `Paragraph`
  * `SpecialCharacters`

* The **feature + sub-feature** convention:

  * `ImageResize`
  * `ListProperties`
  * `TableClipboard`

Plugins must be named in [UpperCamelCase](https://en.wikipedia.org/wiki/CamelCase).

<a id="shortcuts">

### Shortcuts

For local variables **commonly accepted short versions** for long names are fine:

```js
const attr, doc, el, fn, deps, err, id, args, uid, evt, env;
```

The following are **the only short versions accepted for property names**:

```js
this.lang;
this.config;
this.id;
this.env;
```

<a id="acronyms-and-proper-names">

### Acronyms and proper names

Acronyms and, partially, proper names are naturally written in uppercase. This may stand against code style rules described above – especially when there is a need to include an acronym or a proper name in a variable or class name. In such case, one should follow these rules:

* Acronyms:

  * All lowercase if at the beginning of the variable name: `let domError`.
  * Default camel case at the beginning of the class name: `class DomError`.
  * Default camel case inside the variable or class name: `function getDomError()`.

* Proper names:

  * All lowercase if at the beginning of the variable: `let ckeditorError`.
  * Original case if at the beginning of the class name: `class CKEditorError`.
  * Original case inside the variable or class name: `function getCKEditorError()`.

However, two-letter acronyms and proper names (if originally written uppercase) should be uppercase. For example: `getUI`, not `getUi`.

> **Note**
>
> Two most frequently used acronyms which cause problems:
>
> * **DOM** – It should be e.g. `getDomNode()`,
> * **HTML** – It should be e.g. `toHtml()`.

<a id="css-classes">

### CSS classes

CSS class naming pattern is based on [BEM](https://en.bem.info/) methodology and code style. All names are in lowercase with an optional dash (`-`) between the words.

Top–level building **blocks** begin with a mandatory `ck-` prefix:

```css
.ck-dialog
.ck-toolbar
.ck-dropdown-menu
```

**Elements** belonging to the block namespace are delimited by double underscore (`__`):

```css
.ck-dialog__header
.ck-toolbar__group
.ck-dropdown-menu__button-collapser
```

**Modifiers** are delimited by a single underscore (`_`). Key-value modifiers\
follow the `block-or-element_key_value` naming pattern:

```css
/* Block modifier */
.ck-dialog_hidden
/* Element modifier */
.ck-toolbar__group_collapsed
/* Block modifier as key_value  */
.ck-dropdown-menu_theme_some-theme
```

In HTML:

```html
<div class="ck-reset_all ck-dialog ck-dialog_theme_lark ck-dialog_visible">
	<div class="ck-dialog__top ck-dialog__top_small">
		<h1 class="ck-dialog__top-title">Title of the dialog</h1>
		...
	</div>
	...
</div>
```

<a id="id-attributes">

### ID attributes

HTML ID attribute naming pattern follows [CSS classes](#css-classes) naming guidelines. Each ID must begin with the `ck-` prefix and consist of dash–separated (`-`) words in lowercase:

```html
<div id="ck">...</div>
<div id="ck-unique-div">...</div>
<div id="ck-a-very-long-unique-identifier">...</div>
```

<a id="file-names">

### File names

File and directory names must follow a standard that makes their syntax easy to predict:

* All lowercase.

* Only alphanumeric characters are accepted.

* Words are separated by dashes (`-`) ([kebab-case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles)).

  * Code entities are considered single words, so the `DataProcessor` class is defined in the `dataprocessor.js` file.
  * However, a test file covering for “mutations in multi-root editors”: `mutations-in-multi-root-editors.js`.

* HTML files have the `.html` extension.

<a id="examples">

#### Examples

* `ckeditor.js`
* `tools.js`
* `editor.js`
* `dataprocessor.js`
* `build-all.js` and `build-min.js`
* `test-core-style-system.html`

<a id="standard-files">

#### Standard files

Widely used standard files do not obey the above rules:

* `README.md`, `LICENSE.md`, `CONTRIBUTING.md`, `CHANGES.md`
* `.gitignore` and all standard “dot-files”
* `node_modules`

<a id="ckeditor-5-custom-eslint-rules">

## CKEditor 5 custom ESLint rules

In addition to the rules provided by ESLint, CKEditor 5 uses a few custom rules described below.

<a id="importing-between-packages-ckeditor5-rulesno-relative-imports">

### Importing between packages: `ckeditor5-rules/no-relative-imports`

While importing modules from the same package, it is allowed to use relative paths, like this:

```js
// Assume we edit a file located in the path: `packages/ckeditor5-engine/src/model/model.js`

import Position from './position';
import insertContent from './utils/insertcontent';
```

While importing modules from other packages, it is not allowed to use relative paths, and the import must be done using the package name, like this:

👎 Examples of incorrect code for this rule:

```js
// Assume we edit a file located in the path: `packages/ckeditor5-engine/src/model/model.js`

import CKEditorError from '../../../ckeditor5-utils/src/ckeditorerror';
```

Even if the import statement works locally, it will throw an error when developers install packages from npm.

👍 Examples of correct code for this rule:

```js
// Assume we edit a file located in the path: `packages/ckeditor5-engine/src/model/model.js`

import { CKEditorError } from 'ckeditor5';
```

[History of the change.](https://github.com/ckeditor/ckeditor5/issues/7128)

<a id="description-of-an-error-ckeditor5-rulesckeditor-error-message">

### Description of an error: `ckeditor5-rules/ckeditor-error-message`

Each time a new error is created, it needs a description to be displayed on the [error codes](../../support/error-codes.md) page, like this:

👎 Examples of incorrect code for this rule:

```js
// Missing the error's description.

throw new CKEditorError( 'ckeditor5-example-error', this );

// ESLint shouldn't expect the definition of the error as it is already described.

throw new CKEditorError( 'editor-wrong-element', this );
```

👍 Examples of correct code for this rule:

```js
// This error occurs for the first time in the project, so it needs to be defined.

/**
 * Description of why the error was thrown and how to fix the code.
 *
 * @error ckeditor5-example-error
 */
throw new CKEditorError( 'ckeditor5-example-error', this );

// This error is already described, so we don't need to provide its documentation.
// We need to disable ESLint for checking the rule.
// It is a good practice to include a note that explains where it is described.

// Documented in core/editor/editor.js
// eslint-disable-next-line ckeditor5-rules/ckeditor-error-message
throw new CKEditorError( 'editor-wrong-element', this );
```

[History of the change.](https://github.com/ckeditor/ckeditor5/issues/7822)

<a id="cross-package-imports-ckeditor5-rulesno-cross-package-imports">

### Cross package imports: `ckeditor5-rules/no-cross-package-imports`

It is allowed to import modules from other packages:

```js
import { toArray } from '@ckeditor/ckeditor5-utils';
```

However, some packages cannot import modules from CKEditor 5 as it could lead to code duplication and errors in runtime. Hence, the rule disables this kind of import.

Which packages those are is configured per project, through the `disallowedCrossImportsPackages` ESLint setting. No package is restricted by default.

👎 Examples of incorrect code for this rule:

```js
// Assume we edit a file in a package the rule is configured for.

import { toArray } from '@ckeditor/ckeditor5-utils';
import { toArray } from 'ckeditor5';
```

[History of the change.](https://github.com/ckeditor/ckeditor5/issues/9318)

<a id="non-public-members-marked-as-internal-ckeditor5-rulesnon-public-members-as-internal">

### Non-public members marked as @internal : `ckeditor5-rules/non-public-members-as-internal`

> **Warning**
>
> This rule should only be used on `.ts` files.

To remove non-public members from type definitions, use the `@internal` tag in the member’s JSDoc.

Then you can use the `"stripInternal": true` option in `tsconfig.json` to remove them from declaration types.

👎 Examples of incorrect code for this rule:

```ts
class ClassWithSecrets {
	private _shouldNotBeEmitted: string;
}
```

👍 Examples of correct code for this rule:

```ts
class ClassWithSecrets {
	/**
	* @internal
	*/
	private _shouldNotBeEmitted: string;
}
```

<a id="declaring-module-augmentation-for-the-core-package-ckeditor5-rulesallow-declare-module-only-in-augmentation-file">

### Declaring module augmentation for the core package: `ckeditor5-rules/allow-declare-module-only-in-augmentation-file`

> **Warning**
>
> This rule should only be used on `.ts` files.

The main entry points (`index.ts` files) in most modules have a side effect import `import './augmentation'` which uses module augmentation to populate the editor types with information about available plugins, configurations and commands.

This rule forces all `declare module '@ckeditor/ckeditor5-core'` to be defined in this `augmentation.ts` file.

<a id="importing-from-modules-ckeditor5-rulesallow-imports-only-from-main-package-entry-point">

### Importing from modules: `ckeditor5-rules/allow-imports-only-from-main-package-entry-point`

This rule ensures that all imports from the `@ckeditor/*` packages are done through the main package entry points. This is required for the editor types to work properly and to ease migration to the installation methods introduced in CKEditor 5 v42.0.0.

👎 Example of incorrect code for this rule:

```ts
// Importing from the `/src/` folder is not allowed.
import Table from '@ckeditor/ckeditor5-table/src/table';

// Importing from the `/theme/` folder is not allowed.
import BoldIcon from '@ckeditor/ckeditor5-icons/theme/icons/bold.svg';
```

👍 Examples of correct code for this rule:

```ts
// ✔️ Importing from the main entry point is allowed.
import { Table } from 'ckeditor5';
```

Apart from the main entry point, imports from package subpaths listed in the `exports` field of the package are allowed. Additionally, the `allowedImportPatterns` option accepts an array of glob patterns matched against the import path. Imports matching any of these patterns are allowed, too. It defaults to `[ '**/tests/**/_utils*/**' ]`, which allows importing test utilities from other packages.

```js
'ckeditor5-rules/allow-imports-only-from-main-package-entry-point': [ 'error', {
	allowedImportPatterns: [
		'**/tests/**/_utils*/**',
		'**/manual/**/_utils*/**'
	]
} ]
```

<a id="require-as-const-ckeditor5-rulesrequire-as-const-returns-in-methods">

### Require `as const`: `ckeditor5-rules/require-as-const-returns-in-methods`

> **Warning**
>
> This rule should only be used on `.ts` files.

In TypeScript, the types inferred from some values are simplified. For example, the type of `const test = [1, 2, 3];` is `number[]`, but in some cases a more specific type may be needed. Using `as const` can help with this. For example, the type of `const test1 = [1, 2, 3] as const;` is `readonly [1, 2, 3]`.

The `require-as-const-returns-in-methods` rule requires some methods that depend on the exact type of returned data (for example, `'delete'` literal string instead of the generic `string` in the `pluginName` method) to have all return statements with `as const`.

👎 Examples of incorrect code for this rule:

```ts
export default class Delete extends Plugin {
	public static get pluginName(): string {
		return 'Delete';
	}
}
```

```ts
export default class Delete extends Plugin {
	public static get pluginName(): 'Delete' {
		return 'Delete';
	}
}
```

👍 Examples of correct code for this rule:

```ts
export default class Delete extends Plugin {
	public static get pluginName() {
		return 'Delete' as const;
	}
}
```

<a id="disallow-typescript-enums-ckeditor5-rulesno-enum">

### Disallow TypeScript enums: `ckeditor5-rules/no-enum`

> **Warning**
>
> This rule should only be used on `.ts` files.

TypeScript `enum` declarations are disallowed because they emit runtime code that is hard to tree-shake, behave inconsistently between numeric and string enums, and do not mix well with structural typing. Prefer a `const` object combined with a union type derived from its values, which produces a lighter and more predictable output.

👎 Examples of incorrect code for this rule:

```ts
enum Direction {
	Up = 'up',
	Down = 'down'
}
```

👍 Examples of correct code for this rule:

```ts
const Direction = {
	Up: 'up',
	Down: 'down'
} as const;

type Direction = typeof Direction[ keyof typeof Direction ];
```

<a id="imports-within-a-package-ckeditor5-rulesno-scoped-imports-within-package">

### Imports within a package: `ckeditor5-rules/no-scoped-imports-within-package`

All imports defined in every package, that point to a file from the same package, must be relative. You cannot use the scoped imports, if the target file is located in the same package as the import declaration. The resolved scoped import points to the package inside the `node_modules`, but not to the current working directory, and the source code in these two places may differ from each other.

👎 Examples of incorrect code for this rule:

```ts
// Assume we edit a file located in the path: `packages/ckeditor5-alignment/src/alignment.ts`.

// Both imports are incorrect.
import { AlignmentEditing } from '@ckeditor/ckeditor5-alignment';
import AlignmentEditing from '@ckeditor/ckeditor5-alignment/src/alignmentediting';
```

👍 Examples of correct code for this rule:

```ts
// Assume we edit a file located in the path: `packages/ckeditor5-alignment/src/alignment.ts`.

import AlignmentEditing from './alignmentediting';
```

[History of the change.](https://github.com/ckeditor/ckeditor5/issues/14329)

<a id="mandatory-file-extensions-in-imports-ckeditor5-rulesrequire-file-extensions-in-imports">

### Mandatory file extensions in imports: `ckeditor5-rules/require-file-extensions-in-imports`

As required by the [ECMAScript (ESM)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) standard, all imports and re-exports must include a file extension. If they do not include it, this rule will try to automatically detect the correct file extension. It is impossible in these two cases:

* The file extension is different that `.ts`, `.js`, or `.json`.
* The file does not exist in the file system.

The second case is common for the documentation files, because its pieces are located in different directories and repositories. These pieces are merged during the build step, but before that, the imports are technically invalid.

In such cases, you must add the file extension manually. Imports with file extensions are not validated.

<a id="valid-module-tags-ckeditor5-rulesvalidate-module-tag">

### Valid `@module` tags: `ckeditor5-rules/validate-module-tag`

> **Warning**
>
> This rule should only be used on `.ts` files in package `src/` directories.

This rule requires a file-level `@module` JSDoc tag at the top of each TypeScript source file in `packages/*/src`. The tag value must match the file path.

For example, `packages/ckeditor5-feature/src/featureediting.ts` should start with `@module feature/featureediting`. For `index.ts` files, both `@module feature` and `@module feature/index` are allowed. The `augmentation.ts` file is excluded from this rule.

<a id="require-or-disallow-certain-plugin-flags-ckeditor5-rulesckeditor-plugin-flags">

### Require or disallow certain plugin flags: `ckeditor5-rules/ckeditor-plugin-flags`

> **Warning**
>
> This rule should only be used on `.ts` files.

This rule ensures that plugin flags are either correctly set or not set at all. It checks whether the flags have the correct type and value, preventing common mistakes and ensuring compliance with the CKEditor 5 plugin API.

Options:

* `requiredFlags` – (optional) An array of flags that must be set in the plugin.
* `disallowedFlags` – (optional) An array of flags that must not be set in the plugin.

The example configuration below requires the `isFooPlugin` flag to be set to `true` and disallows the `isBarPlugin` flag:

```json
{
	"requiredFlags": [
		{
			"name": "isFooPlugin",
			"returnValue": true
		}
	],
	"disallowedFlags": [ "isBarPlugin" ]
}
```

👎 Examples of incorrect code for this rule:

```ts
export default class MyPlugin extends Plugin {
	static get pluginName() {
		return 'MyPlugin';
	}

	public static override get isBarPlugin(): false {
		return false;
	}
}
```

The `isBarPlugin` flag is disallowed, and the plugin has it set to `false`. Additionally, the `isFooPlugin` flag is required but not defined.

👍 Examples of correct code for this rule:

```ts
export default class MyPlugin extends Plugin {
	static get pluginName() {
		return 'MyPlugin';
	}

	public static override get isFooPlugin(): true {
		return true;
	}
}
```

The `isFooPlugin` flag is required and set to `true`, and the `isBarPlugin` flag is not defined.

<a id="svg-imports-only-in-the-ckeditorckeditor5-icons-package">

### SVG imports only in the `@ckeditor/ckeditor5-icons` package

This rule ensures that SVG files are imported and exported only in the `@ckeditor/ckeditor5-icons` package. This package should include all icons used in CKEditor 5.

<a id="css-imports-only-in-the-main-package-entry-point">

### CSS imports only in the main package entry point

This rule ensures that CSS files are imported only in the main package entry point (`src/index.ts`). Each package imports `theme/index-editor.css` followed by `theme/index-content.css`. Individual source modules must not import CSS files.

<a id="valid-changelog-entries">

### Valid changelog entries

This rule ensures that changelog entry files are populated with proper data and a clear description of the change. For a full guide on how to populate changelog entries, see the [Changelog entries](changelog-entries.md) guide.

<a id="disallow-hardcoded-root-literals-ckeditor5-rulesno-literal-dollar-root">

### Disallow hardcoded `$root` literals: `ckeditor5-rules/no-literal-dollar-root`

This rule disallows the literal `'$root'` string anywhere it could be used as a schema context. Hardcoding `'$root'` is silently wrong when [`config.root.modelElement`](../../api/module_core_editor_editorconfig-RootConfig.md#member-modelElement) is customized: the runtime root no longer matches the literal, and any schema check or upcast against it operates against the wrong element name. See the [Root types](../../getting-started/setup/root-types.md) guide for an overview of the available root model element types.

The rule also reports two specific patterns that have a name-agnostic replacement and provides an auto-fix for them:

* `node.is( 'element', '$root' )` → `node.is( 'rootElement' )`
* `node.name === '$root'` / `node.name !== '$root'` → `node.is( 'rootElement' )` / `!node.is( 'rootElement' )`

Options:

* `allowedPackages` – (optional) A list of package name fragments whose source files are exempt from the rule. Used for `ckeditor5-engine` and `ckeditor5-core` where `'$root'` is the canonical schema element name being defined.
* `allowedCalls` – (optional) A list of method names where a `'$root'` literal argument is permitted (e.g. `is` so that `node.is( '$root' )` is allowed alongside the canonical `node.is( 'rootElement' )` form).

The rule also has a narrow built-in exception: a `'$root'` literal under a `context` property of an options object passed to a view-event listener method (`listenTo()`, `on()`, `once()`, `off()`) is not flagged, because in that position `'$root'` is a view-tree bubbling target — not the schema element name.

👎 Examples of incorrect code for this rule:

```ts
const ROOT_NAME = '$root';

if ( node.is( 'element', '$root' ) ) { /* ... */ }

if ( modelElement.name === '$root' ) { /* ... */ }

writer.addRoot( newRoot, '$root' );
```

👍 Examples of correct code for this rule:

```ts
if ( node.is( 'rootElement' ) ) { /* ... */ }

if ( modelElement.is( 'rootElement' ) ) { /* ... */ }

// View-event listener `context` option — `'$root'` here is a view-tree bubbling target.
this.listenTo( viewDocument, 'arrowKey', handler, { context: '$root' } );

// Adding attributes to the default root element is the documented escape hatch.
schema.extend( '$root', { allowAttributes: [ 'foo' ] } );
```

If the literal `'$root'` is the right value at a call site, opt out with an `// eslint-disable-next-line` comment that explains why. A common case is a feature that is intentionally scoped to roots whose `modelElement` is the default `$root` and is not meant to operate on roots configured with a custom `modelElement`. Such a feature may register its schema only against `$root` on purpose:

```ts
// eslint-disable-next-line ckeditor5-rules/no-literal-dollar-root -- feature is registered only on the default `$root` by design
model.schema.register( 'myFeatureElement', { isBlock: true, allowIn: '$root' } );
```

<a id="require-explicit-schema-context-ckeditor5-rulesrequire-explicit-data-context">

### Require explicit schema context: `ckeditor5-rules/require-explicit-data-context`

This rule flags calls to engine APIs whose schema-context argument silently defaults to `'$root'` when omitted. Like the rule above, the silent default is wrong as soon as a root is configured with a custom [`modelElement`](../../api/module_core_editor_editorconfig-RootConfig.md#member-modelElement), and the failure is hard to diagnose because no error is raised.

The rule reports calls to these APIs when their context argument is missing:

* [`data.parse( data, context )`](../../api/module_engine_controller_datacontroller-DataController.md#function-parse)
* [`data.toModel( view, context )`](../../api/module_engine_controller_datacontroller-DataController.md#function-toModel)
* [`document.createRoot( elementName, rootName )`](../../api/module_engine_model_document-ModelDocument.md#function-createRoot)
* [`writer.addRoot( rootName, elementName )`](../../api/module_engine_model_writer-ModelWriter.md#function-addRoot)
* [`upcastDispatcher.convert( viewElementOrFragment, writer, context )`](../../api/module_engine_conversion_upcastdispatcher-UpcastDispatcher.md#function-convert)

The receiver patterns are matched syntactically (the receiver’s final accessed property must be `data` / `document` / `upcastDispatcher`, or a bare identifier `writer` for `addRoot`). This intentionally excludes `MultiRootEditor#addRoot( rootName, options? )`, which has a different signature and resolves the model element name from its options object.

👎 Examples of incorrect code for this rule:

```ts
const fragment = editor.data.parse( html );
const fragment = editor.data.toModel( view );

editor.model.document.createRoot();

editor.model.change( writer => {
	writer.addRoot( 'main' );
} );

editor.data.upcastDispatcher.convert( viewFragment, writer );
```

👍 Examples of correct code for this rule:

```ts
const fragment = editor.data.parse( html, '$documentFragment' );
const fragment = editor.data.toModel( view, editor.model.document.getRoot()! );

editor.model.document.createRoot( '$inlineRoot' );

editor.model.change( writer => {
	writer.addRoot( 'main', '$inlineRoot' );
} );

editor.data.upcastDispatcher.convert( viewFragment, writer, [ '$root' ] );
```

If the default `'$root'` context is intentional (for example, an internal editor whose only root always uses the default model element), opt out with an `// eslint-disable-next-line` comment that explains why.

<a id="disallow-shadow-unsafe-dom-apis-ckeditor5-rulesno-shadow-unsafe-dom-apis">

### Disallow shadow-unsafe DOM APIs: `ckeditor5-rules/no-shadow-unsafe-dom-apis`

This rule reports DOM APIs that stop working correctly once the editor is placed inside a shadow root. None of them throw an error. Each one resolves against the top-level document, so it either finds nothing or returns the shadow host instead of the node inside it. The problem surfaces later as a feature that silently stops reacting, which makes it difficult to trace back to the API that caused it.

The reported APIs fall into the following groups:

* **Active element and selection** – `document.activeElement` and `getSelection()`. Neither can see into a shadow root: both report the host of the tree rather than the focused or selected node inside it. Because the rule matches the access path rather than the value, it recognizes the top-level document however it is written – `document`, `window.document`, or any `*.ownerDocument`. `getSelection()` is caught on the window forms too, whether a bare call or through `window.`, `self.`, or `*.defaultView.`, because it is defined on both `Window` and `Document`, while `activeElement` belongs to `DocumentOrShadowRoot`. Use the [`getActiveElement()`](../../api/module_utils_dom_getactiveelement.md#function-getActiveElement) and [`getSelection()`](../../api/module_utils_dom_getselection.md#function-getSelection) helpers instead, which read from the node’s own root.
* **Containment and traversal** – `contains()` called on the top-level document or its body, and every read of `.parentNode` or `.parentElement`. All of them stop at the boundary. Use `isConnected` or [`containsNode()`](../../api/module_utils_dom_containsnode.md#function-containsNode) in place of `contains()`, and [`getParentNode()`](../../api/module_utils_dom_getparentnode.md#function-getParentNode) or [`getParentElement()`](../../api/module_utils_dom_getparentelement.md#function-getParentElement) in place of the raw properties.
* **Element lookup** – `querySelector()`, `querySelectorAll()`, `getElementById()`, `getElementsByTagName()`, `getElementsByClassName()`, and `getElementsByName()` called on the top-level document or its body. The contents of a shadow root are not reachable from the document, so these lookups find nothing. Query the editor’s own root instead.
* **Point resolution** – `document.elementFromPoint()` and `document.elementsFromPoint()` ignore shadow content, so the point must be resolved through the element’s own root. `caretPositionFromPoint()` needs a `{ shadowRoots }` option to resolve carets inside a shadow root and is reported when that option is missing.
* **Root discovery** – `.shadowRoot` and `composedPath()`. Both are reported on every use, not only when they serve to discover the root. Keep a held reference to the root, or call `getRootNode()` instead.
* **Document listeners** – `mouseenter`, `mouseleave`, `pointerenter`, `pointerleave`, and `scroll` listeners added to or removed from the top-level document. They do not fire for events that happen inside another shadow root.
* **Body mounting** – `document.body.appendChild()`. Mount it into the body collection root instead.
* **Tree traversal** – `createTreeWalker()` and `createNodeIterator()` rooted at the top-level document or its body. Neither one descends into shadow roots. Only the root argument matters here, not the object the factory is called on.
* **Retargeted event properties** – any read or write of `relatedTarget`. It is retargeted at a shadow boundary, so it does not point at the node the pointer actually came from or went to.

Some cases are deliberately left out. Legacy `caretRangeFromPoint()` is never reported, because it accepts coordinates only and has no shadow-aware form. It is used exactly where `caretPositionFromPoint()` is unavailable, so there is nothing to switch to. Destructuring, for example `const { relatedTarget } = domEvent;`, is not covered either, because the rule matches property access rather than bindings. Private class fields are left alone as well – `this.#parentNode`, `this.#shadowRoot`, and `this.#relatedTarget` are ordinary class state, not DOM properties. Conversely, `relatedTarget` is reported on every access without checking which object it belongs to, since the name is rarely used for anything else.

Wrapping an access does not sidestep the rule. It matches through optional chaining, non-null assertions, `as` casts, bracket notation, and template-literal property keys, so `document?.querySelector( '.x' )` and `document[ 'querySelector' ]( '.x' )` are both reported.

A bare call to a function that resolves to a local binding, whether imported or declared in the file, is never reported, even when it shares a name with a global. That is how the shadow-aware helpers themselves pass the rule. A name introduced only by a `/* global */` directive comment does not count as a local binding and is still reported.

👎 Examples of incorrect code for this rule:

```ts
const active = document.activeElement;
const selection = window.getSelection();
const isInside = document.body.contains( element );
const parent = element.parentNode;
const toolbarElement = document.querySelector( '.ck-toolbar' );

document.addEventListener( 'scroll', handler, { capture: true } );
document.caretPositionFromPoint( x, y );
```

👍 Examples of correct code for this rule:

```ts
import { getActiveElement, getSelection, containsNode, getParentNode } from '@ckeditor/ckeditor5-utils';

const active = getActiveElement( element );
const selection = getSelection( element );
const isInside = containsNode( container, element );
const parent = getParentNode( element );

// Resolve the root once, then query it and listen on it.
const editorRoot = element.getRootNode() as Document | ShadowRoot;
const toolbarElement = editorRoot.querySelector( '.ck-toolbar' );

editorRoot.addEventListener( 'scroll', handler, { capture: true } );
document.caretPositionFromPoint( x, y, { shadowRoots } );
```

If a raw API is the correct call at a given site, opt out with an `// eslint-disable-next-line` comment that explains why. The shadow-aware helpers that wrap these APIs are the main case.

<a id="ckeditor-5-custom-css-eslint-rules">

## CKEditor 5 custom CSS ESLint rules

In addition to the rules provided by ESLint and its CSS plugin, CKEditor 5 uses a few custom CSS rules described below.

<a id="names-of-css-variables-within-ck-content-ckeditor5-rulesck-content-variable-name">

### Names of CSS variables within `.ck-content`: `ckeditor5-rules/ck-content-variable-name`

This rule aims to enforce convention of all variables targeting styling of elements within the editor (`.ck-content`) using a common prefix (`--ck-content-*`)

👎 Example of incorrect code for this rule:

```css
.ck-content {
	width: var(--variable-name);
}
```

👍 Example of correct code for this rule:

```css
.ck-content {
	width: var(--ck-content-variable-name);
}
```

[History of the change.](https://github.com/ckeditor/ckeditor5/issues/18805)

<a id="content-stylesheet-placement-ckeditor5-rulescontent-styles-in-index-content">

### Content stylesheet placement: `ckeditor5-rules/content-styles-in-index-content`

This rule requires content styles scoped with `.ck-content` to be placed in `theme/index-content.css`. Editor-only content hosts, such as `.ck-editor__editable.ck-content`, stay in editor stylesheets.

<a id="editor-stylesheet-placement-ckeditor5-rulesno-editor-styles-in-index-content">

### Editor stylesheet placement: `ckeditor5-rules/no-editor-styles-in-index-content`

This rule prevents editor UI and editing-view selectors from being placed in `theme/index-content.css`. The content entry point may also contain supporting custom properties, font definitions, and keyframes.

A bare `:host` selector that declares only custom properties is allowed. Together with `:root`, it marks the root scope the stylesheet resolves its custom properties in, so `:root, :host { --ck-content-*: ... }` is accepted here. A parameterized `:host(...)` still counts as an editor selector. See the [`ckeditor5-rules/require-host-with-root-selector`](#root-selectors-require-host-ckeditor5-rulesrequire-host-with-root-selector) rule for why the two selectors are paired.

<a id="selector-specificity-order-ckeditor5-rulesno-descending-specificity">

### Selector specificity order: `ckeditor5-rules/no-descending-specificity`

This rule reports selectors with lower specificity placed after overriding selectors with higher specificity that target the same element. Such a selector cannot win the cascade where both selectors apply, so the resulting order is misleading to readers and fragile during refactoring. It reimplements Stylelint’s [`no-descending-specificity`](https://stylelint.io/user-guide/rules/no-descending-specificity/) rule for ESLint.

Selectors are compared only within the same context (the same nesting parent and at-rule conditions) and only when they share the same key selector, that is, the last compound selector ignoring pseudo-classes. For example, `a:hover` and `a` are compared with each other, while `a::before` is not compared with `a`.

👎 Example of incorrect code for this rule:

```css
.ck-toolbar a {
	color: var(--ck-color-link);
}

a {
	color: var(--ck-color-text);
}
```

👍 Example of correct code for this rule:

```css
a {
	color: var(--ck-color-text);
}

.ck-toolbar a {
	color: var(--ck-color-link);
}
```

<a id="custom-property-references-require-var-ckeditor5-rulesno-missing-var-function">

### Custom property references require `var()`: `ckeditor5-rules/no-missing-var-function`

This rule reports custom property references used as declaration values without the `var()` function. A bare reference is not substituted by the browser, so the declaration silently does not work. It reimplements Stylelint’s [`custom-property-no-missing-var-function`](https://stylelint.io/user-guide/rules/custom-property-no-missing-var-function/) rule for ESLint.

Properties whose values legitimately contain dashed identifiers are not checked. This covers transition targets (`transition`, `transition-property`, and `will-change`) and the naming properties of anchor positioning, scroll-driven animations, and view transitions (for example, `anchor-name` or `view-transition-name`).

👎 Example of incorrect code for this rule:

```css
.ck-button {
	color: --ck-color-text;
	--ck-button-color: --ck-color-text;
}
```

👍 Example of correct code for this rule:

```css
.ck-button {
	color: var(--ck-color-text);
	--ck-button-color: var(--ck-color-text);
}
```

<a id="root-selectors-require-host-ckeditor5-rulesrequire-host-with-root-selector">

### `:root` selectors require `:host`: `ckeditor5-rules/require-host-with-root-selector`

A `:root` selector matches nothing inside a shadow root, so a stylesheet that anchors its custom properties on `:root` alone leaves every one of them undefined for an editor mounted inside one. This rule requires each `:root` selector to be paired with `:host`, which matches the shadow host from inside the shadow tree and matches nothing in the light DOM. Pairing the two lets a single stylesheet resolve its tokens in both contexts.

Only a bare `:host` satisfies the rule. A parameterized `:host(...)` targets specific hosts rather than the root scope, so it does not count.

The rule is autofixable. The fix adds `:host` as a new selector after `:root`, indented to match it.

👎 Example of incorrect code for this rule:

```css
:root {
	--ck-bookmark-icon-hover-fill-color: var(--ck-color-widget-hover-border);
}
```

👍 Example of correct code for this rule:

```css
:root,
:host {
	--ck-bookmark-icon-hover-fill-color: var(--ck-color-widget-hover-border);
}
```

---

Full index of the CKEditor 5 documentation: [llms.txt](../../../llms.txt)
