# Integrating CKEditor 5 with Vue.js 3+ from npm

CKEditor 5 has an official Vue integration that you can use to add a rich text editor to your application. It provides a `<ckeditor>` component with two-way data binding through `v-model`. The component works with multiple editor types, including classic and decoupled (document). For the multi-root editor, use the dedicated [multi-root editor component](vue-multiroot-npm.md). This guide will help you install and configure it to use the npm distribution of CKEditor 5.

> **Create your own CKEditor 5**
>
> Check out our interactive Builder to quickly get a taste of CKEditor 5. It offers an easy-to-use user interface to help you configure, preview, and download the editor suited to your needs.
>
> * editor type,
> * the features you need,
> * the preferred framework (React, Angular, Vue or Vanilla JS),
> * the preferred distribution method.
>
> You get ready-to-use code tailored to your needs!
>
> [Check out our interactive Builder](https://builder.ckeditor.com/?redirect=docs)

<a id="quick-start">

## Quick start

This guide assumes that you already have a Vue project. If you do not have one, see the [Vue documentation](https://vuejs.org/guide/quick-start) to learn how to create it.

Start by installing the following packages:

`ckeditor5` – contains all open-source plugins and features for CKEditor 5.

```bash
npm install ckeditor5
```

`ckeditor5-premium-features` – contains premium plugins and features for CKEditor 5. Depending on your configuration and chosen plugins, you might not need it.

```bash
npm install ckeditor5-premium-features
```

`@ckeditor/ckeditor5-vue` – the [CKEditor 5 WYSIWYG editor component for Vue](https://www.npmjs.com/package/@ckeditor/ckeditor5-vue).

```bash
npm install @ckeditor/ckeditor5-vue
```

With these packages installed, create a new Vue component called `Editor.vue`. It will use the `<ckeditor>` component to run the editor. The following example shows a single file component with open-source and premium CKEditor 5 plugins.

> **Note**
>
> Starting from version 44.0.0, the `licenseKey` property is required to use the editor. If you use a self-hosted editor from npm:
>
> * You must either comply with the GPL or
> * Obtain a license for [self-hosting distribution](../../../licensing/license-key-and-activation.md).
>
> You can set up [a free trial](https://portal.ckeditor.com/checkout?plan=free) to test the editor and evaluate the self-hosting.

```vue
<template>
	<ckeditor
		v-model="data"
		:editor="ClassicEditor"
		:config="config"
	/>
</template>

<script setup>
import { ref, computed } from 'vue';
import { ClassicEditor, Essentials, Paragraph, Bold, Italic } from 'ckeditor5';
import { FormatPainter } from 'ckeditor5-premium-features';
import { Ckeditor } from '@ckeditor/ckeditor5-vue';

import 'ckeditor5/ckeditor5.css';
import 'ckeditor5-premium-features/ckeditor5-premium-features.css';

const data = ref( '<p>Hello world!</p>' );

const config = computed( () => {
	return {
		licenseKey: '<YOUR_LICENSE_KEY>',
		plugins: [ Essentials, Paragraph, Bold, Italic, FormatPainter ],
		toolbar: [ 'undo', 'redo', '|', 'bold', 'italic', '|', 'formatPainter' ]
	};
} );
</script>
```

Now, you can import and use the `Editor.vue` component anywhere in your application.

```vue
<template>
	<Editor />
</template>
```

If you use Nuxt.js with server-side rendering enabled, remember to wrap the `<Editor>` component in the `<ClientOnly>` component to avoid issues with the editor calling browser-specific APIs on the server.

```vue
<template>
	<ClientOnly>
		<Editor />
	</ClientOnly>
</template>
```

<a id="component-directives">

## Component directives

<a id="editor">

### `editor`

This directive specifies the editor to be used by the component. It must directly reference the editor constructor to be used in the template.

```vue
<template>
	<ckeditor :editor="ClassicEditor" />
</template>

<script setup>
import { ClassicEditor } from 'ckeditor5';
import { Ckeditor } from '@ckeditor/ckeditor5-vue';
</script>
```

<a id="tag-name">

### `tag-name`

> **Warning**
>
> The `tag-name` directive is deprecated in favor of `config.root.element` (or `config.roots.main.element`). The new configuration option lets you customize the tag name, classes, inline styles, and HTML attributes of the editable element. See the [Using an inline editor](#using-an-inline-editor) section below for details.

By default, the editor component creates a `<div>` container which is used as an element passed to the editor (for example, [`ClassicEditor#element`](../../../../api/module_editor-classic_classiceditorui-ClassicEditorUI.md#member-element)). The element can be configured, so for example to create a `<textarea>`, use the following directive:

```vue
<ckeditor :editor="editor" tag-name="textarea" />
```

<a id="v-model">

### `v-model`

A [standard directive](https://v3.vuejs.org/guide/component-basics.html#using-v-model-on-components) for form inputs in Vue. Unlike [`model-value`](#model-value), it creates a two–way data binding, which:

* Sets the initial editor content.
* Automatically updates the state of the application as the editor content changes (for example, as the user types).
* Can be used to set the editor content when necessary.

```vue
<template>
	<ckeditor :editor="ClassicEditor" v-model="data" />
	<button @click="emptyEditor">Empty the editor</button>

	<h2>Editor data</h2>
	<code>{{ data }}</code>
</template>

<script setup>
import { ref } from 'vue';
import { ClassicEditor } from 'ckeditor5';
import { Ckeditor } from '@ckeditor/ckeditor5-vue';

const data = ref( '<p>Hello world!</p>' );

function emptyEditor() {
	data.value = '';
}
</script>
```

In the above example, the `data` property will be updated automatically as the user types and the content changes. It can also be used to change (as in `emptyEditor()`) or set the initial content of the editor.

If you only want to execute an action when the editor data changes, use the [`input`](#input) event.

<a id="model-value">

### `model-value`

Allows a one–way data binding that sets the content of the editor. Unlike [`v-model`](#v-model), the value will not be updated when the content of the editor changes.

```vue
<template>
	<ckeditor :editor="ClassicEditor" :model-value="data" />
</template>

<script setup>
import { ref } from 'vue';
import { ClassicEditor } from 'ckeditor5';
import { Ckeditor } from '@ckeditor/ckeditor5-vue';

const data = ref( '<p>Hello world!</p>' );
</script>
```

To execute an action when the editor data changes, use the [`input`](#input) event.

<a id="config">

### `config`

Specifies the [configuration](../../../../api/module_core_editor_editorconfig-EditorConfig.md) of the editor.

```vue
<template>
	<ckeditor :editor="ClassicEditor" :config="config" />
</template>

<script setup>
import { computed } from 'vue';
import { ClassicEditor, Essentials, Paragraph, Bold, Italic } from 'ckeditor5';
import { Ckeditor } from '@ckeditor/ckeditor5-vue';

const config = computed( () => {
	return {
		licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
		plugins: [ Essentials, Paragraph, Bold, Italic ],
		toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ]
	};
} );
</script>
```

<a id="disabled">

### `disabled`

This directive controls the [`isReadOnly`](../../../../api/module_core_editor_editor-Editor.md#member-isReadOnly) property of the editor.

It sets the initial read–only state of the editor and changes it during its lifecycle.

```vue
<template>
	<ckeditor :editor="ClassicEditor" :disabled="disabled" />
</template>

<script setup>
import { ref } from 'vue';
import { ClassicEditor } from 'ckeditor5';
import { Ckeditor } from '@ckeditor/ckeditor5-vue';

const disabled = ref( true );
</script>
```

<a id="disable-two-way-data-binding">

### `disable-two-way-data-binding`

Allows disabling the two-way data binding mechanism. The default value is `false`.

The reason for introducing this option is performance issues in large documents. After enabling this flag, the `v-model` directive will no longer update the connected value whenever the editor’s data is changed.

This option allows the integrator to disable the default behavior and only call the [`editor.getData()`](../../../../api/module_core_editor_editor-Editor.md#function-getData) method on demand, which prevents the slowdowns. You can read more in the [relevant issue](https://github.com/ckeditor/ckeditor5-vue/issues/246).

```vue
<template>
	<ckeditor
		:editor="ClassicEditor"
		:disable-two-way-data-binding="disableTwoWayDataBinding"
	/>
</template>

<script setup>
import { ref } from 'vue';
import { ClassicEditor } from 'ckeditor5';
import { Ckeditor } from '@ckeditor/ckeditor5-vue';

const disableTwoWayDataBinding = ref( true );
</script>
```

<a id="watchdog-config">

### `watchdog-config`

Allows passing a configuration object to the underlying [`EditorWatchdog`](../../../../api/module_watchdog_editorwatchdog-EditorWatchdog.md). By default, the `<ckeditor>` component automatically wraps the editor with a watchdog that detects crashes and restarts the editor to recover lost content. Use this prop to customize the watchdog behavior, such as the number of allowed crashes before the watchdog gives up, or the minimum time between crashes.

```vue
<template>
	<ckeditor
		:editor="ClassicEditor"
		:watchdog-config="watchdogConfig"
	/>
</template>

<script setup>
import { ClassicEditor } from 'ckeditor5';
import { Ckeditor } from '@ckeditor/ckeditor5-vue';

const watchdogConfig = {
	crashNumberLimit: 5,
	minimumNonErrorTimePeriod: 2000
};
</script>
```

See the [`WatchdogConfig` API](../../../../api/module_watchdog_watchdog-WatchdogConfig.md) for the full list of available options.

This prop has no effect when [`disable-watchdog`](#disable-watchdog) is set to `true`.

<a id="disable-watchdog">

### `disable-watchdog`

Allows disabling the built-in watchdog. The default value is `false`.

By default, the `<ckeditor>` component wraps the editor with CKEditor 5’s [`EditorWatchdog`](../../../../api/module_watchdog_editorwatchdog-EditorWatchdog.md), which automatically detects and recovers from editor crashes. Setting `disable-watchdog` to `true` opts out of this behavior - the editor will run without crash recovery.

When the watchdog is disabled, the [`ready`](#ready) and [`destroy`](#destroy) events will each fire at most once during the component’s lifetime, and the [`error`](#error) event will never be emitted.

```vue
<template>
	<ckeditor :editor="ClassicEditor" :disable-watchdog="true" />
</template>

<script setup>
import { ClassicEditor } from 'ckeditor5';
import { Ckeditor } from '@ckeditor/ckeditor5-vue';
</script>
```

<a id="component-events">

## Component events

<a id="ready">

### `ready`

Corresponds to the [`ready`](../../../../api/module_core_editor_editor-Editor.md#event-ready) editor event.

```vue
<ckeditor :editor="editor" @ready="onEditorReady" />
```

> **Note**
>
> When the watchdog is active (the default), this event can fire **multiple times** during the component’s lifetime - once after the initial mount and again after each watchdog-triggered editor restart. If you need one-time initialization logic (for example, inserting a toolbar into the DOM for the Document editor type), make sure your handler is idempotent or guard it with a flag.

<a id="focus">

### `focus`

Corresponds to the [`focus`](../../../../api/module_engine_view_document-ViewDocument.md#event-focus) editor event.

```vue
<ckeditor :editor="editor" @focus="onEditorFocus" />
```

<a id="blur">

### `blur`

Corresponds to the [`blur`](../../../../api/module_engine_view_document-ViewDocument.md#event-blur) editor event.

```vue
<ckeditor :editor="editor" @blur="onEditorBlur" />
```

<a id="input">

### `input`

Corresponds to the [`change:data`](../../../../api/module_engine_model_document-ModelDocument.md#event-change:data) editor event.

```vue
<ckeditor :editor="editor" @input="onEditorInput" />
```

<a id="error">

### `error`

Fired when an error is detected by the watchdog - either during editor initialization or at runtime.

```vue
<ckeditor :editor="editor" @error="onEditorError" />
```

The event handler receives two arguments:

* `error` – the `Error` object describing what went wrong.

* `details` – an object with the following properties:

  * `phase: 'initialization' | 'runtime'` – `'initialization'` when the error occurred during `Editor.create()`, or `'runtime'` for errors caught during normal operation.
  * `causesRestart: boolean` – whether the watchdog will attempt to restart the editor. When `false`, no automatic restart is scheduled (for example, the crash limit was reached, or restarting does not apply to this error).

```vue
<template>
	<ckeditor :editor="ClassicEditor" @error="onEditorError" />
</template>

<script setup>
import { ClassicEditor } from 'ckeditor5';
import { Ckeditor } from '@ckeditor/ckeditor5-vue';

function onEditorError( error, { phase, causesRestart } ) {
	if ( phase === 'runtime' && causesRestart ) {
		console.warn( 'Editor crashed: the watchdog is restarting it.', error );
	} else {
		console.error( 'Editor error: the watchdog will not restart the editor automatically.', error );
	}
}
</script>
```

This event is not emitted when [`disable-watchdog`](#disable-watchdog) is set to `true`.

<a id="destroy">

### `destroy`

Corresponds to the [`destroy`](../../../../api/module_core_editor_editor-Editor.md#event-destroy) editor event.

```vue
<ckeditor :editor="editor" @destroy="onEditorDestroy" />
```

> **Note**
>
> Because the destruction of the editor is promise–driven, this event can be fired before the actual promise resolves.

> **Note**
>
> When the watchdog is active (the default), this event can fire **multiple times** during the component’s lifetime - once for each editor instance destroyed during a watchdog restart. It is **not** fired when the component unmounts before the editor finishes initializing. If you need to react to component unmount, use Vue’s `onBeforeUnmount` lifecycle hook instead.

<a id="how-to">

## How to?

<a id="using-the-document-editor-type">

### Using the Document editor type

If you use the [Document (decoupled) editor](../../../../framework/deep-dive/ui/document-editor.md) in your application, you need to [manually add the editor toolbar to the DOM](../../../../api/module_editor-decoupled_decouplededitor-DecoupledEditor.md#static-function-create).

Since accessing the editor toolbar is not possible until after the editor instance is [ready](../../../../api/module_core_editor_editor-Editor.md#event-ready), put your toolbar insertion code in a method executed upon the [`ready`](#ready) event of the component, like in the following example:

```vue
<template>
	<ckeditor :editor="DecoupledEditor" @ready="onReady" />
</template>

<script setup>
import { DecoupledEditor } from 'ckeditor5';
import { Ckeditor } from '@ckeditor/ckeditor5-vue';

import 'ckeditor5/ckeditor5.css';

function onReady( editor )  {
	// Insert the toolbar before the editable area.
	editor.ui.getEditableElement().parentElement.insertBefore(
		editor.ui.view.toolbar.element,
		editor.ui.getEditableElement()
	);
}
</script>
```

<a id="using-an-inline-editor">

### Using an inline editor

Single-root editors such as [`InlineEditor`](../../../../api/module_editor-inline_inlineeditor-InlineEditor.md), [`BalloonEditor`](../../../../api/module_editor-balloon_ballooneditor-BalloonEditor.md), and [`DecoupledEditor`](../../../../api/module_editor-decoupled_decouplededitor-DecoupledEditor.md) can be configured as inline editors that accept only inline content (text, bold, italic, links) instead of blocks. This is useful for short fields such as titles, captions, or single-line inputs.

Set [`root.modelElement`](../../../../api/module_core_editor_editorconfig-RootConfig.md#member-modelElement) to `'$inlineRoot'` to restrict the root to inline content. Optionally, provide a custom [`root.element`](../../../../api/module_core_editor_editorconfig-RootConfig.md#member-element) to render the editable host as a specific tag (for example, `<h1>` for a title) instead of the default `<div>`.

```vue
<template>
	<ckeditor :editor="BalloonEditor" :config="config" />
</template>

<script setup>
import { BalloonEditor, Essentials, Bold, Italic } from 'ckeditor5';

import 'ckeditor5/ckeditor5.css';

const config = {
	licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
	plugins: [ Essentials, Bold, Italic ],
	toolbar: [ 'bold', 'italic' ],
	root: {
		element: 'h1',
		modelElement: '$inlineRoot',
		initialData: 'Document title',
		placeholder: 'Enter title...'
	}
};
</script>
```

The `root.element` property accepts:

* A tag name string, for example `'h1'` or `'section'`.
* A descriptor object with `name`, `classes`, `styles`, and `attributes` fields.

Without `modelElement: '$inlineRoot'`, only the host tag changes – the schema still permits blocks inside the root.

> **Important**
>
> The `<ckeditor>` component always renders a `<div>` host for `ClassicEditor`, regardless of `root.element`. Classic editor wraps its toolbar and editable inside its own structure. Use `InlineEditor`, `BalloonEditor`, or `DecoupledEditor` to control the host element.

<a id="using-the-editor-with-collaboration-plugins">

### Using the editor with collaboration plugins

We provide a **ready-to-use integration** featuring collaborative editing in a Vue application:

* [CKEditor 5 with real-time collaboration features and revision history features](https://github.com/ckeditor/ckeditor5-collaboration-samples/tree/master/real-time-collaboration-for-vue)
* [CKEditor 5 with offline comments, track changes and revision history features](https://github.com/ckeditor/ckeditor5-collaboration-samples/tree/master/collaboration-for-vue)

It is not mandatory to build applications on top of the above sample, however, it should help you get started.

<a id="localization">

### Localization

CKEditor 5 supports [multiple UI languages](../../../setup/ui-language.md), and so does the official Vue component. Follow the instructions below to translate CKEditor 5 in your Vue application.

Similarly to CSS style sheets, both packages have separate translations. Import them as shown in the example below. Then, pass them to the `translations` array inside the `config` prop in the component:

```vue
<script setup>
import { computed } from 'vue';
import coreTranslations from 'ckeditor5/translations/es.js';
import premiumFeaturesTranslations from 'ckeditor5-premium-features/translations/es.js';

const config = computed( () => {
	return {
		translations: [ coreTranslations, premiumFeaturesTranslations ],
		// Other configuration options
	};
} );
</script>
```

For more information, refer to the [Setting the UI language](../../../setup/ui-language.md) guide.

<a id="jest-testing">

### Jest testing

You can use Jest as a test runner in Vue apps. Unfortunately, Jest does not use a real browser. Instead, it runs tests in Node.js that uses JSDOM. JSDOM is not a complete DOM implementation, and while it is sufficient for standard apps, it cannot polyfill all the DOM APIs that CKEditor 5 requires.

For testing CKEditor 5, it is recommended to use testing frameworks that utilize a real browser and provide a complete DOM implementation. Some popular options include:

* [Vitest](https://vitest.dev/)
* [Playwright](https://playwright.dev/)
* [Cypress](https://www.cypress.io/)

These frameworks offer better support for testing CKEditor 5 and provide a more accurate representation of how the editor behaves in a real browser environment.

If this is not possible and you still want to use Jest, you can mock some of the required APIs. Below is an example of how to mock some of the APIs used by CKEditor 5:

```javascript
import { TextEncoder } from 'util';

beforeAll( () => {
	window.TextEncoder = TextEncoder;

	window.scrollTo = jest.fn();

	window.ResizeObserver = class ResizeObserver {
		observe() {}
		unobserve() {}
		disconnect() {}
	};

	for (const key of ['InputEvent', 'KeyboardEvent']) {
		window[key].prototype.getTargetRanges = () => {
			const range = new StaticRange({
				startContainer: document.body.querySelector('.ck-editor__editable p')!,
				startOffset: 0,
				endContainer: document.body.querySelector('.ck-editor__editable p')!,
				endOffset: 0,
			});

			return [range];
		};
	}

	const getClientRects = () => ({
		item: () => null,
		length: 0,
		[Symbol.iterator]: function* () {}
	});

	Range.prototype.getClientRects = getClientRects;
	Element.prototype.getClientRects = getClientRects;

	if ( !Document.prototype.createElementNS ) {
		Document.prototype.createElementNS = ( namespace, name ) => {
			const element = document.createElement( name );
			element.namespaceURI = namespace;
			return element;
		};
	}
} );
```

These mocks should be placed before the tests that use CKEditor 5. They are imperfect and may not cover all the cases, but they should be sufficient for basic initialization and rendering editor. Remember that they are not a replacement for proper browser testing.

<a id="contributing-and-reporting-issues">

## Contributing and reporting issues

The source code of this component is available on GitHub in <https://github.com/ckeditor/ckeditor5-vue>.

<a id="next-steps">

## Next steps

* See how to manipulate the editor’s data in the [Getting and setting data](../../../setup/getting-and-setting-data.md) guide.
* Refer to further guides in the [setup section](../../../setup/configuration.md) to see how to customize your editor further.
* Check the [features category](../../../../features/index.md) to learn more about individual features.

---

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