# Mentions (autocompletion)

The mention feature enables smart autocompletion based on user input. When you type a pre-configured marker, such as `@` or `#`, a panel displays with autocomplete suggestions.

<a id="demo">

## Demo

You can type the “@” character to invoke the mention autocomplete UI. The demo below is configured to suggest a static list of names (`Barney`, `Lily`, `Marry Ann`, `Marshall`, `Robin`, and `Ted`).

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

> **Note**
>
> This demo presents a limited set of features. Visit the [feature-rich editor example](../examples/builds-custom/full-featured-editor.md) to see more in action.
>
> You can also check out the [more advanced example](../examples/framework/chat-with-mentions.md) of the mention feature used in a chat application.

You can read more about possible implementations of the mention feature in a [dedicated blog post](https://ckeditor.com/blog/mentions-in-ckeditor-5-feature-of-the-month/).

<a id="installation">

## Installation

After [installing the editor](../getting-started/installation/cloud/quick-start.md), add the feature to your plugin list and toolbar configuration:

**NPM**

```js
import { ClassicEditor, Mention } from 'ckeditor5';

ClassicEditor
	.create( {
		licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
		plugins: [ Mention, /* ... */ ],
		mention: {
			// Configuration.
		}
	} )
	.then( /* ... */ )
	.catch( /* ... */ );
```

**CDN**

```js
const { ClassicEditor, Mention } = CKEDITOR;

ClassicEditor
	.create( {
		licenseKey: '<YOUR_LICENSE_KEY>',
		plugins: [ Mention, /* ... */ ],
		mention: {
			// Configuration.
		}
	} )
	.then( /* ... */ )
	.catch( /* ... */ );
```

<a id="unsupported-contexts">

## Unsupported contexts

The mention autocompletion is automatically disabled inside [code blocks](code-blocks.md). Typing a mention marker (such as `@`) inside a code block will not trigger the autocomplete panel. This also applies to features built on top of mentions, such as [slash commands](slash-commands.md) and [emoji](emoji.md) autocompletion.

If you want to disable this behavior and allow mention autocompletion inside code blocks, you can override it using the [`Schema#checkAttribute`](../api/module_engine_model_schema-ModelSchema.md#event-checkAttribute) event:

```js
editor.model.schema.on( 'checkAttribute', ( evt, args ) => {
	const context = args[ 0 ];
	const attributeName = args[ 1 ];

	if ( attributeName === 'mention' && context.endsWith( 'codeBlock $text' ) ) {
		evt.stop();
		evt.return = true;
	}
}, { priority: 'high' } );
```

<a id="configuration">

## Configuration

The minimal configuration of the mention feature requires defining a [`feed`](../api/module_mention_mentionconfig-MentionFeed.md) and a [`marker`](../api/module_mention_mentionconfig-MentionFeed.md). You can also define the `minimumCharacters` parameter, setting the number of letters after which the autocomplete panel will show up. Moreover, feed items’ IDs may include whitespaces.

The code snippet below was used to configure the demo above. It defines the list of names that the editor will autocomplete after the user types the “@” character.

```js
ClassicEditor
	.create( {
		// ... Other configuration options ...
		mention: {
			feeds: [
				{
					marker: '@',
					feed: [ '@Barney', '@Lily', '@Marry Ann', '@Marshall', '@Robin', '@Ted' ],
					minimumCharacters: 1
				}
			]
		}
	} )
	.then( /* ... */ )
	.catch( /* ... */ );
```

Additionally, you can configure:

* How the item is rendered in the autocomplete panel (via setting [`itemRenderer`](../api/module_mention_mentionconfig-MentionFeed.md)). See [Customizing the autocomplete list](#customizing-the-autocomplete-list).
* How the item is converted during the [conversion](../framework/architecture/editing-engine.md#conversion). See [Customizing the output](#customizing-the-output).
* Multiple feeds. The demo above uses only one feed, which is triggered by the `'@'` character. You can define multiple feeds but they must use different markers. For example, you can use `'@'` for people and `'#'` for tags.

<a id="providing-the-feed">

### Providing the feed

The [`feed`](../api/module_mention_mentionconfig-MentionFeed.md) can be provided as:

* A static array – Good for scenarios with a relatively small set of autocomplete items.
* A callback – Provides more control over the returned list of items.

When using a callback you can return a `Promise` that resolves with the list of [matching feed items](../api/module_mention_mentionconfig-MentionFeedItem.md). These can be simple strings or plain objects with at least the `name` property. The other properties of this object can later be used when [customizing the autocomplete list](#customizing-the-autocomplete-list) or [customizing the output](#customizing-the-output).

> **Note**
>
> When using external resources to obtain the feed it is recommended to add some caching mechanism so subsequent calls for the same suggestion would load faster.
>
> You can also consider adding the `minimumCharacters` option to the feed configuration so the editor will call the feed callback after some minimum number of characters typed instead of an action on a marker alone.

The callback receives the query text which should be used to filter item suggestions. It should return a `Promise` and resolve it with an array of items that match the feed text.

```js
ClassicEditor
	.create( {
		// ... Other configuration options ...
		mention: {
			feeds: [
				{
					marker: '@',
					feed: getFeedItems
				}
			}
		]
	} )
	.then( /* ... */ )
	.catch( /* ... */ );

const items = [
	{ id: '@swarley', userId: '1', name: 'Barney Stinson', link: 'https://www.imdb.com/title/tt0460649/characters/nm0000439' },
	{ id: '@lilypad', userId: '2', name: 'Lily Aldrin', link: 'https://www.imdb.com/title/tt0460649/characters/nm0004989' },
	{ id: '@marry', userId: '3', name: 'Marry Ann Lewis', link: 'https://www.imdb.com/title/tt0460649/characters/nm1130627' },
	{ id: '@marshmallow', userId: '4', name: 'Marshall Eriksen', link: 'https://www.imdb.com/title/tt0460649/characters/nm0781981' },
	{ id: '@rsparkles', userId: '5', name: 'Robin Scherbatsky', link: 'https://www.imdb.com/title/tt0460649/characters/nm1130627' },
	{ id: '@tdog', userId: '6', name: 'Ted Mosby', link: 'https://www.imdb.com/title/tt0460649/characters/nm1102140' }
];

function getFeedItems( queryText ) {
	// As an example of an asynchronous action, return a promise
	// that resolves after a 100ms timeout.
	// This can be a server request or any sort of delayed action.
	return new Promise( resolve => {
		setTimeout( () => {
			const itemsToDisplay = items
				// Filter out the full list of all items to only those matching the query text.
				.filter( isItemMatching )
				// Return 10 items max - needed for generic queries when the list may contain hundreds of elements.
				.slice( 0, 10 );

			resolve( itemsToDisplay );
		}, 100 );
	} );

	// Filtering function - it uses the `name` and `username` properties of an item to find a match.
	function isItemMatching( item ) {
		// Make the search case-insensitive.
		const searchString = queryText.toLowerCase();

		// Include an item in the search results if the name or username includes the current user input.
		return (
			item.name.toLowerCase().includes( searchString ) ||
			item.id.toLowerCase().includes( searchString )
		);
	}
}
```

A full, working demo with all possible customizations and its source code is available [at the end of this section](#fully-customized-mention-feed).

<a id="customizing-the-autocomplete-list">

### Customizing the autocomplete list

<a id="styling">

#### Styling

The items displayed in the autocomplete list can be customized by defining the [`itemRenderer`](../api/module_mention_mentionconfig-MentionFeed.md) callback.

This callback takes a feed item (it contains at least the `name` property) and must return a new DOM element.

```js
ClassicEditor
	.create( {
		// ... Other configuration options ...
		mention: {
			feeds: [
				{
					feed: [ /* ... */ ],
					// Define the custom item renderer.
					itemRenderer: customItemRenderer
				}
			]
		}
	} )
	.then( /* ... */ )
	.catch( /* ... */ );

function customItemRenderer( item ) {
	const itemElement = document.createElement( 'span' );

	itemElement.classList.add( 'custom-item' );
	itemElement.id = `mention-list-item-id-${ item.userId }`;
	itemElement.textContent = `${ item.name } `;

	const usernameElement = document.createElement( 'span' );

	usernameElement.classList.add( 'custom-item-username' );
	usernameElement.textContent = item.id;

	itemElement.appendChild( usernameElement );

	return itemElement;
}
```

A full, working demo with all possible customizations and its source code is available [at the end of this section](#fully-customized-mention-feed).

<a id="list-length">

#### List length

The number of items displayed in the autocomplete list can be customized by defining the [`dropdownLimit`](../api/module_mention_mentionconfig-MentionConfig.md#member-dropdownLimit) option.

```js
ClassicEditor
	.create( {
		// ... Other configuration options ...
		mention: {
			// Define the custom number of visible mentions.
			dropdownLimit: 4
			feeds: [
				{ /* ... */ }
				// More feeds.
				// ...
			]
		}
	} )
	.then( /* ... */ )
	.catch( /* ... */ );
```

A full, working demo with all possible customizations and its source code is available [at the end of this section](#fully-customized-mention-feed).

<a id="customizing-the-text-inserted-into-the-editor">

### Customizing the text inserted into the editor

You can control the text inserted into the editor when creating a mention via the [`text`](../api/module_mention_mentionconfig-MentionFeedObjectItem.md) property in the mention configuration.

```js
ClassicEditor
	.create( {
		// ... Other configuration options ...
		mention: {
			feeds: [
				// Feed items as objects.
				{
					marker: '@',
					feed: [
						{
							id: '@Barney',
							fullName: 'Barney Stinson',
							// Custom text to be inserted into the editor
							text: 'Swarley'
						},
						// ...
					]
				},
			]
		}
	} )
	.then( /* ... */ )
	.catch( /* ... */ );
```

The string that you specify in this property will be displayed in the editor when a mention is created.

<a id="customizing-the-output">

### Customizing the output

To change the markup generated by the editor for mentions, you can overwrite the default converter of the mention feature. To do that, you must specify both [upcast](../api/module_engine_conversion_upcastdispatcher-UpcastDispatcher.md) and [downcast](../api/module_engine_conversion_downcastdispatcher-DowncastDispatcher.md) converters using [`ViewAttributeElement`](../api/module_engine_view_attributeelement-ViewAttributeElement.md).

The example below defines a plugin that overrides the default output:

```html
<span data-mention="@Ted" class="mention">@Ted</span>
```

To a link:

```html
<a class="mention" data-mention="@Ted" data-user-id="5" href="https://www.imdb.com/title/tt0460649/characters/nm1102140">@tdog</a>
```

The converters must be defined with a `'high'` priority to be executed before the [link](link.md) feature’s converter and before the default converter of the mention feature. A mention is stored in the model as a [text attribute](../framework/architecture/editing-engine.md#text-attributes) that stores an object (see [`MentionFeedItem`](../api/module_mention_mentionconfig-MentionFeedItem.md)).

To control how the mention element is wrapped by other attribute elements (like bold, italic, etc) set its [`priority`](../api/module_engine_view_attributeelement-ViewAttributeElement.md#member-priority). To replicate default plugin behavior and make mention to be wrapped by other elements set priority to `20`.

By default, attribute elements that are next to each other and have the same value will be rendered as a single HTML element. To prevent this, the model attribute value object exposes a unique ID of each inserted mention to the model as `uid`. To prevent merging subsequent mentions, set it as [`id`](../api/module_engine_view_attributeelement-ViewAttributeElement.md#member-id). The `uid` is persisted in the data output as the `data-mention-uid` attribute to guarantee that the same HTML always produces the same model. During clipboard (copy/cut) output, `data-mention-uid` is omitted so that pasted mentions receive fresh unique IDs.

**Note:** The feature prevents copying fragments of existing mentions. If only a part of a mention is selected, it will be copied as plain text. The internal converter with the [`'highest'` priority](../api/module_utils_priorities-PrioritiesType.md#member-highest) controls this behavior. We do not recommend adding mention converters with the `'highest'` priority to avoid collisions and quirky results.

```js
ClassicEditor
	.create( {
		// ... Other configuration options ...
		plugins: [ Mention, MentionCustomization, /* ... */ ], // Add the custom mention plugin function.
		mention: {
			// Configuration.
			// ...
		}
	} )
	.then( /* ... */ )
	.catch( /* ... */ );

function MentionCustomization( editor ) {
	// The upcast converter will convert view <a class="mention" href="" data-user-id="">
	// elements to the model 'mention' text attribute.
	editor.conversion.for( 'upcast' ).elementToAttribute( {
		view: {
			name: 'a',
			key: 'data-mention',
			classes: 'mention',
			attributes: {
				href: true,
				'data-user-id': true
			}
		},
		model: {
			key: 'mention',
			value: viewItem => {
				// The mention feature expects that the mention attribute value
				// in the model is a plain object with a set of additional attributes.
				// In order to create a proper object use the toMentionAttribute() helper method:
				const mentionAttribute = editor.plugins.get( 'Mention' ).toMentionAttribute( viewItem, {
					// Add any other properties that you need.
					link: viewItem.getAttribute( 'href' ),
					userId: viewItem.getAttribute( 'data-user-id' )
				} );

				return mentionAttribute;
			}
		},
		converterPriority: 'high'
	} );

	// Downcast the model 'mention' text attribute to a view <a> element.
	editor.conversion.for( 'downcast' ).attributeToElement( {
		model: 'mention',
		view: ( modelAttributeValue, { writer, options } ) => {
			// Do not convert empty attributes (lack of value means no mention).
			if ( !modelAttributeValue ) {
				return;
			}

			return writer.createAttributeElement( 'a', {
				class: 'mention',
				'data-mention': modelAttributeValue.id,
				'data-user-id': modelAttributeValue.userId,
				'href': modelAttributeValue.link,
				// Omit `data-mention-uid` in clipboard (copy/cut) to prevent UIDs duplication.
				...( !options.isClipboardPipeline && { 'data-mention-uid': modelAttributeValue.uid } )
			}, {
				// Make mention attribute to be wrapped by other attribute elements.
				priority: 20,
				// Prevent merging mentions together in clipboard (when `data-mention-uid` is not available).
				id: modelAttributeValue.uid
			} );
		},
		converterPriority: 'high'
	} );
}
```

A full, working demo with all possible customizations and its source code is available [at the end of this section](#fully-customized-mention-feed).

<a id="fully-customized-mention-feed">

### Fully customized mention feed

Below is an example of a customized mention feature that:

* Uses a feed of items with additional properties (`id`, `username`, `link`).
* Renders custom item views in the autocomplete panel.
* Converts a mention to an `<a>` element instead of a `<span>`.
* Limits the number of mentions to four elements.

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

<a id="source-code">

#### Source code

```js
ClassicEditor
	.create( {
		attachTo: document.querySelector( '#snippet-mention-customization' ),
		// ... Other configuration options ...
		plugins: [ Mention, MentionCustomization, /* ... */ ],
		mention: {
			dropdownLimit: 4,
			feeds: [
				{
					marker: '@',
					feed: getFeedItems,
					itemRenderer: customItemRenderer
				}
			]
		}
	} )
	.then( editor => {
		window.editor = editor;
	} )
	.catch( err => {
		console.error( err.stack );
	} );

function MentionCustomization( editor ) {
	// The upcast converter will convert <a class="mention" href="" data-user-id="">
	// elements to the model 'mention' attribute.
	editor.conversion.for( 'upcast' ).elementToAttribute( {
		view: {
			name: 'a',
			key: 'data-mention',
			classes: 'mention',
			attributes: {
				href: true,
				'data-user-id': true
			}
		},
		model: {
			key: 'mention',
			value: viewItem => {
				// The mention feature expects that the mention attribute value
				// in the model is a plain object with a set of additional attributes.
				// In order to create a proper object, use the toMentionAttribute helper method:
				const mentionAttribute = editor.plugins.get( 'Mention' ).toMentionAttribute( viewItem, {
					// Add any other properties that you need.
					link: viewItem.getAttribute( 'href' ),
					userId: viewItem.getAttribute( 'data-user-id' )
				} );

				return mentionAttribute;
			}
		},
		converterPriority: 'high'
	} );

	// Downcast the model 'mention' text attribute to a view <a> element.
	editor.conversion.for( 'downcast' ).attributeToElement( {
		model: 'mention',
		view: ( modelAttributeValue, { writer, options } ) => {
			// Do not convert empty attributes (lack of value means no mention).
			if ( !modelAttributeValue ) {
				return;
			}

			return writer.createAttributeElement( 'a', {
				class: 'mention',
				'data-mention': modelAttributeValue.id,
				'data-user-id': modelAttributeValue.userId,
				'href': modelAttributeValue.link,
				// Omit `data-mention-uid` in clipboard (copy/cut) to prevent UIDs duplication.
				...( !options.isClipboardPipeline && { 'data-mention-uid': modelAttributeValue.uid } )
			}, {
				// Make mention attribute to be wrapped by other attribute elements.
				priority: 20,
				// Prevent merging mentions together in clipboard (when `data-mention-uid` is not available).
				id: modelAttributeValue.uid
			} );
		},
		converterPriority: 'high'
	} );
}

const items = [
	{ id: '@swarley', userId: '1', name: 'Barney Stinson', link: 'https://www.imdb.com/title/tt0460649/characters/nm0000439' },
	{ id: '@lilypad', userId: '2', name: 'Lily Aldrin', link: 'https://www.imdb.com/title/tt0460649/characters/nm0004989' },
	{ id: '@marry', userId: '3', name: 'Marry Ann Lewis', link: 'https://www.imdb.com/title/tt0460649/characters/nm1130627' },
	{ id: '@marshmallow', userId: '4', name: 'Marshall Eriksen', link: 'https://www.imdb.com/title/tt0460649/characters/nm0781981' },
	{ id: '@rsparkles', userId: '5', name: 'Robin Scherbatsky', link: 'https://www.imdb.com/title/tt0460649/characters/nm1130627' },
	{ id: '@tdog', userId: '6', name: 'Ted Mosby', link: 'https://www.imdb.com/title/tt0460649/characters/nm1102140' }
];

function getFeedItems( queryText ) {
	// As an example of an asynchronous action, return a promise
	// that resolves after a 100ms timeout.
	// This can be a server request or any sort of delayed action.
	return new Promise( resolve => {
		setTimeout( () => {
			const itemsToDisplay = items
				// Filter out the full list of all items to only those matching the query text.
				.filter( isItemMatching )
				// Return 10 items max - needed for generic queries when the list may contain hundreds of elements.
				.slice( 0, 10 );

			resolve( itemsToDisplay );
		}, 100 );
	} );

	// Filtering function - it uses `name` and `username` properties of an item to find a match.
	function isItemMatching( item ) {
		// Make the search case-insensitive.
		const searchString = queryText.toLowerCase();

		// Include an item in the search results if name or username includes the current user input.
		return (
			item.name.toLowerCase().includes( searchString ) ||
			item.id.toLowerCase().includes( searchString )
		);
	}
}

function customItemRenderer( item ) {
	const itemElement = document.createElement( 'span' );

	itemElement.classList.add( 'custom-item' );
	itemElement.id = `mention-list-item-id-${ item.userId }`;
	itemElement.textContent = `${ item.name } `;

	const usernameElement = document.createElement( 'span' );

	usernameElement.classList.add( 'custom-item-username' );
	usernameElement.textContent = item.id;

	itemElement.appendChild( usernameElement );

	return itemElement;
}
```

<a id="colors-and-styles">

### Colors and styles

<a id="using-css-variables">

#### Using CSS variables

The mention feature uses the power of [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables) which are defined in the [default theme style sheet](https://github.com/ckeditor/ckeditor5/blob/master/packages/ckeditor5-mention/theme/index-content.css). Thanks to that, mention styles can be [easily customized](../framework/deep-dive/ui/theme-customization.md):

```css
:root {
	/* Make the mention background blue. */
	--ck-content-color-mention-background: hsla(220, 100%, 54%, 0.4);

	/* Make the mention text dark grey. */
	--ck-content-color-mention-text: hsl(0, 0%, 15%);
}
```

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

<a id="comments-with-mentions">

### Comments with mentions

It is possible to configure the Mentions feature to work with the [Comments feature](collaboration/comments/comments.md). Here you can find [detailed guidance on that matter](collaboration/annotations/annotations-custom-configuration.md#comment-editor-configuration).

<a id="related-features">

## Related features

In addition to enabling mentions, you may want to check the following productivity features:

* [Automatic text transformation](text-transformation.md) – Lets you automatically turn snippets such as `(tm)` into `™` and `"foo"` into `“foo”`.
* [Autolink](link.md#autolink-feature) – Turns the links and email addresses typed or pasted into the editor into active URLs.
* [Autoformatting](autoformat.md) – Lets you quickly apply formatting to the content you are writing.
* [Emoji](emoji.md) – Lets you quickly insert desired emoji.

<a id="common-api">

## Common API

The [`Mention`](../api/module_mention_mention-Mention.md) plugin registers:

* The `'mention'` command implemented by [`MentionCommand`](../api/module_mention_mentioncommand-MentionCommand.md).

  You can insert a mention element by executing the following code:

  ```js
  editor.execute( 'mention', { marker: '@', mention: '@John' } );
  ```

> **Note**
>
> We recommend using the official [CKEditor 5 inspector](../framework/development-tools/inspector.md) for development and debugging. It will give you tons of useful information about the state of the editor such as internal data structures, selection, commands, and many more.

<a id="contribute">

## Contribute

The source code of the feature is available on GitHub at <https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-mention>.

---

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