# Integrating CKEditor 5 with React rich text editor component from npm

CKEditor 5 has an official React integration that you can use to add a rich text editor to your application. It provides a `<CKEditor>` component that you configure with props for the editor build, its configuration, and event handlers. The component works with multiple editor types, including classic, inline, and decoupled (document). For the multi-root editor, use the dedicated [multi-root editor hook](react-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 React project. If you do not have one, see the [React documentation](https://react.dev/learn/start-a-new-react-project) to learn how to create it.

First, install the CKEditor 5 packages:

* `ckeditor5` – package with open-source plugins and features.
* `ckeditor5-premium-features` – package with premium plugins and features.

Depending on your configuration and chosen plugins, you may need to install the first or both packages.

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

Then, install the [CKEditor 5 WYSIWYG editor component for React](https://www.npmjs.com/package/@ckeditor/ckeditor5-react):

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

Use the `<CKEditor>` component inside your project. The below example shows how to use the component with open-source and premium 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.

```jsx
import { CKEditor } from '@ckeditor/ckeditor5-react';
import { ClassicEditor, Essentials, Paragraph, Bold, Italic } from 'ckeditor5';
import { FormatPainter } from 'ckeditor5-premium-features';

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

function App() {
	return (
		<CKEditor
			editor={ ClassicEditor }
			config={ {
				licenseKey: '<YOUR_LICENSE_KEY>',
				plugins: [ Essentials, Paragraph, Bold, Italic, FormatPainter ],
				toolbar: [ 'undo', 'redo', '|', 'bold', 'italic', '|', 'formatPainter' ],
				root: {
					initialData: '<p>Hello from CKEditor 5 in React!</p>'
				},
			} }
		/>
	);
}

export default App;
```

Remember to import the necessary style sheets. The `ckeditor5` package contains the styles for open-source features, while the `ckeditor5-premium-features` package contains the premium features styles.

<a id="component-properties">

## Component properties

The `<CKEditor>` component supports the following properties:

* `editor` (required) – The [`Editor`](../../../../api/module_core_editor_editor-Editor.md) constructor to use.
* `data` – The initial data for the created editor. See the [Getting and setting data](../../../setup/getting-and-setting-data.md) guide.
* `config` – The editor configuration. See the [Configuration](../../../setup/configuration.md) guide.
* `id` – The editor ID. When this property changes, the component restarts the editor with new data instead of setting it on an initialized editor.
* `disabled` – A Boolean value. The [`editor`](../../../../api/module_core_editor_editor-Editor.md) is being switched to read-only mode if the property is set to `true`.
* `onReady` – A function called when the editor is ready with an [`editor`](../../../../api/module_core_editor_editor-Editor.md) instance.
* `onAfterDestroy` – A function called after the successful destruction of an editor instance rendered by the component. The component is not guaranteed to be mounted when this function is called.
* `onChange` – A function called when the editor data has changed. See the [`editor.model.document#change:data`](../../../../api/module_engine_model_document-ModelDocument.md#event-change:data) event.
* `onBlur` – A function called when the editor was blurred. See the [`editor.editing.view.document#blur`](../../../../api/module_engine_view_document-ViewDocument.md#event-blur) event.
* `onFocus` – A function called when the editor was focused. See the [`editor.editing.view.document#focus`](../../../../api/module_engine_view_document-ViewDocument.md#event-focus) event.
* `onError` – A function called when an error is reported for the editor, either during the initialization or at runtime. It receives two arguments: the error instance and the error details. Error details is an object that contains one property:
  * `{String} phase`: `'initialization'|'runtime'` – Informs when the error has occurred (during the editor or context initialization, or after the initialization).

The editor event callbacks (`onChange`, `onBlur`, `onFocus`) receive two arguments:

1. An [`EventInfo`](../../../../api/module_utils_eventinfo-EventInfo.md) object.
2. An [`Editor`](../../../../api/module_core_editor_editor-Editor.md) instance.

A reported error does not stop the editor. Its state may no longer be consistent, so do not leave the error unhandled. Nothing is restarted and no data is restored for you, so what happens next is your application’s decision. `<CKEditorContext>` has an `onError` of its own, with the same two arguments, for the errors of the context rather than of one of its editors.

The [error handling](../../../setup/error-handling.md) guide covers the options: telling the user and switching the editor to read-only, recreating it, and recovering its content. If you are moving off the Watchdog, the [migrating from the Watchdog](../../../../updating/guides/migration-from-watchdog.md) guide shows how to recreate the editor by changing the component’s `key`, and when to do it.

<a id="context-feature">

## Context feature

The [`@ckeditor/ckeditor5-react`](https://www.npmjs.com/package/@ckeditor/ckeditor5-react) package provides a ready-to-use component for the [context feature](../../../../features/collaboration/context-and-collaboration-features.md) that is useful when used together with some [CKEditor 5 collaboration features](../../../../features/collaboration/collaboration.md).

```jsx
import { ClassicEditor, Context, Bold, Essentials, Italic, Paragraph } from 'ckeditor5';
import { CKEditor, CKEditorContext } from '@ckeditor/ckeditor5-react';

import 'ckeditor5/ckeditor5.css';

function App() {
  return (
	<CKEditorContext context={ Context }>
	  <CKEditor
		editor={ ClassicEditor }
		config={ {
		  licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
		  plugins: [ Essentials, Bold, Italic, Paragraph ],
		  toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ],
		} }
		data='<p>Hello from the first editor working with the context!</p>'
		onReady={ ( editor ) => {
		  // You can store the "editor" and use when it is needed.
		  console.log( 'Editor 1 is ready to use!', editor );
		} }
	  />

	  <CKEditor
		editor={ ClassicEditor }
		config={ {
		  licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
		  plugins: [ Essentials, Bold, Italic, Paragraph ],
		  toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ],
		} }
		data='<p>Hello from the second editor working with the context!</p>'
		onReady={ ( editor ) => {
		  // You can store the "editor" and use when it is needed.
		  console.log( 'Editor 2 is ready to use!', editor );
		} }
	  />
	</CKEditorContext>
  );
}

export default App;
```

The `CKEditorContext` component supports the following properties:

* `context` (required) – [The CKEditor 5 context class](../../../../api/module_core_context-Context.md).
* `config` – The CKEditor 5 context configuration.
* `isLayoutReady` – A property that delays the context creation when set to `false`. It creates the context and the editor children once it is `true` or unset. Useful when the CKEditor 5 annotations or a presence list are used.
* `id` – The context ID. When this property changes, the component restarts the context with its editor and reinitializes it based on the current configuration.
* `onChangeInitializedEditors` – A function called when any editor is initialized or destroyed in the tree. It receives a dictionary of fully initialized editors, where the key is the value of the `contextItemMetadata.name` property set on the `CKEditor` component. The editor’s ID is the key if the `contextItemMetadata` property is absent. Additional data can be added to the `contextItemMetadata` in the `CKEditor` component, which will be passed to the `onChangeInitializedEditors` function.
* `onReady` – A function called when the context is ready and all editors inside were initialized with the `context` instance.
* `onError` – A function called when an error is reported for the context, either during the initialization or at runtime. It receives two arguments: the error instance and the error details. Error details is an object that contains one property:
  * `{String} phase`: `'initialization'|'runtime'` – Informs when the error has occurred (during the editor or context initialization, or after the initialization).

> **Note**
>
> An example build that exposes both context and classic editor can be found in the [CKEditor 5 collaboration sample](https://github.com/ckeditor/ckeditor5-collaboration-samples/tree/master/real-time-collaboration-comments-outside-of-editor).

<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), you need to [add the toolbar to the DOM manually](../../../../api/module_editor-decoupled_decouplededitor-DecoupledEditor.md#static-function-create):

```jsx
import { useEffect, useRef, useState } from 'react';
import { DecoupledEditor, Bold, Essentials, Italic, Paragraph } from 'ckeditor5';
import { CKEditor } from '@ckeditor/ckeditor5-react';

import 'ckeditor5/ckeditor5.css';

function App() {
	const editorToolbarRef = useRef( null );
	const [ isMounted, setMounted ] = useState( false );

	useEffect( () => {
		setMounted( true );

		return () => {
			setMounted( false );
		};
	}, [] );

	return (
		<div>
			<div ref={ editorToolbarRef }></div>
			<div>
				{ isMounted && (
					<CKEditor
						editor={ DecoupledEditor }
						data='<p>Hello from CKEditor 5 decoupled editor!</p>'
						config={ {
							  licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
							plugins: [ Bold, Italic, Paragraph, Essentials ],
							toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ]
						} }
						onReady={ ( editor ) => {
							if ( editorToolbarRef.current ) {
								editorToolbarRef.current.appendChild( editor.ui.view.toolbar.element );
							}
						}}
						onAfterDestroy={ ( editor ) => {
							if ( editorToolbarRef.current ) {
								Array.from( editorToolbarRef.current.children ).forEach( child => child.remove() );
							}
						}}
					/>
				) }
			</div>
		</div>
	);
}

export default App;
```

<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>`.

```jsx
import { CKEditor } from '@ckeditor/ckeditor5-react';
import { BalloonEditor, Essentials, Bold, Italic } from 'ckeditor5';

import 'ckeditor5/ckeditor5.css';

function App() {
	return (
		<CKEditor
			editor={ BalloonEditor }
			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...'
				}
			} }
		/>
	);
}

export default App;
```

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-inside-a-shadow-root">

### Using inside a shadow root

Rendering the editor inside a shadow root isolates it from the styles of the host page. The bundler injects `ckeditor5.css` into `<head>`, where the shadow root cannot see it, so import the style sheet as a string and adopt it in the root as a [constructed style sheet](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/adoptedStyleSheets) instead. The whole editor UI, including the body collection that holds balloons and dropdown panels, stays inside that root, so this one style sheet covers all of it. This happens because [`config.ui.overlayContainer`](../../../../api/module_core_editor_editorconfig-UiConfig.md#member-overlayContainer) is not set, so the editor falls back to the root it is in and logs the `ui-overlay-container-not-configured` warning. For a more robust setup, set your own overlay container and load the same style sheets into it, as described in the [Where the floating user interface mounts](../../../setup/shadow-dom.md#where-the-floating-user-interface-mounts) section of the Shadow DOM guide.

```jsx
import { useCallback, useRef, useState } from 'react';
import { createPortal } from 'react-dom';

import { CKEditor } from '@ckeditor/ckeditor5-react';
import { ClassicEditor, Essentials, Bold, Italic, Paragraph } from 'ckeditor5';

// The `?inline` query makes the bundler return the style sheet as a string.
import editorStyles from 'ckeditor5/ckeditor5.css?inline';

const styleSheet = new CSSStyleSheet();

styleSheet.replaceSync( editorStyles );

function App() {
	const [ shadowRoot, setShadowRoot ] = useState( null );

	// `attachShadow()` can be called only once per element, and `host.shadowRoot` cannot
	// report an existing root in closed mode, so track the call instead.
	const attached = useRef( false );

	const hostRef = useCallback( host => {
		if ( !host || attached.current ) {
			return;
		}

		attached.current = true;

		const root = host.attachShadow( { mode: 'open' } );

		root.adoptedStyleSheets = [ styleSheet ];

		setShadowRoot( root );
	}, [] );

	return (
		<div ref={ hostRef }>
			{ shadowRoot && createPortal(
				<CKEditor
					editor={ ClassicEditor }
					config={ {
						licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
						plugins: [ Essentials, Paragraph, Bold, Italic ],
						toolbar: [ 'bold', 'italic' ],
						root: {
							initialData: '<p>Hello from a shadow root!</p>'
						}
					} }
				/>,
				shadowRoot
			) }
		</div>
	);
}

export default App;
```

The `?inline` query is supported by Vite. Other bundlers spell it differently – in webpack 5, import the style sheet with a `?raw` query loaded with the `asset/source` type, and exclude that query from your CSS rule. See [Loading the editor styles](../../../setup/shadow-dom.md#loading-the-editor-styles) in the Shadow DOM guide for details.

> **Important**
>
> `attachShadow()` can be called only once per element. If the mode of the root has to change at runtime, give the component holding the host a `key` so that React remounts it.

An override of a `--ck-*` variable on `:root` has no effect on an editor inside a shadow root, so put it on the shadow host instead. The [Shadow DOM](../../../setup/shadow-dom.md) guide explains why, and covers the known limitations.

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

### Using the editor with collaboration plugins

We provide a few **ready-to-use integrations** featuring collaborative editing in React applications:

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

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

<a id="localization">

### Localization

CKEditor 5 supports [multiple UI languages](../../../setup/ui-language.md), and so does the official React component. Follow the instructions below to translate CKEditor 5 in your React 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 CKEditor 5 component.

```jsx
import { ClassicEditor } from 'ckeditor5';
import { CKEditor } from '@ckeditor/ckeditor5-react';
// More imports...

import coreTranslations from 'ckeditor5/translations/es.js';
import premiumFeaturesTranslations from 'ckeditor5-premium-features/translations/es.js';

function App() {
	return (
		<CKEditor
			editor={ ClassicEditor }
			config={ {
				// ... Other configuration options ...
				translations: [ coreTranslations, premiumFeaturesTranslations ],
				root: {
					initialData: '<p>Hola desde CKEditor 5 en React!</p>'
				}
			} }
		/>
	);
}

export default App;
```

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

<a id="jest-testing">

### Jest testing

Jest is the default test runner used by many React 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:

```jsx
import { TextEncoder } from 'util';
import React, { useRef } from 'react';
import { render, waitFor, screen } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';

import { DecoupledEditor, Essentials, Paragraph } from 'ckeditor5';
import { CKEditor } from '@ckeditor/ckeditor5-react';

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;
		};
	}
} );

const SomeComponent = ( { value, onChange } ) => {
	const editorRef = useRef();

	return (
		<div
			style={{
				border: '1px solid black',
				padding: 10,
			}}
		>
			<CKEditor
				editor={ DecoupledEditor }
				config={{
					plugins: [ Essentials, Paragraph ],
				}}
				onReady={ (editor) => {
					editorRef.current = editor;
				} }
				data={ value }
				onChange={ () => {
					onChange( editorRef.current?.getData() );
				} }
			/>
		</div>
	);
};

it( 'renders', async () => {
	render( <SomeComponent value="this is some content" /> );

	await waitFor( () => expect( screen.getByText( /some content/ ) ).toBeTruthy());
} );

it( 'updates', async () => {
	const onChange = jest.fn();
	render( <SomeComponent value="this is some content" onChange={onChange} /> );

	await waitFor( () => expect( screen.getByText( /some content/ ) ).toBeTruthy() );

	await userEvent.click( document.querySelector( '[contenteditable="true"]' ) );

	userEvent.keyboard( 'more stuff' );

	await waitFor( () => expect( onChange ).toHaveBeenCalled() );
} );
```

The mocks presented above only test two basic scenarios, and more will likely need to be added, which may change with each version of the editor.

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

## Contributing and reporting issues

The source code of rich text editor component for React is available on GitHub in <https://github.com/ckeditor/ckeditor5-react>.

<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)
