Report an issue

guideCustom template for annotations

A custom template is a middle ground between the default UI and a completely custom UI of your own.

# How it works

This solution lets you alter the HTML structure of comment and suggestion annotations. Thanks to that you can, for example:

  • Add new CSS classes or HTML elements to enable more complex styling.
  • Re-arrange annotation views.
  • Add new UI elements, linked with your custom features.
  • Remove UI elements.

It is highly recommended to get familiar with the CKEditor 5 UI library API and framework/architecture/ui-library guide before continuing.

The examples below are based on a working editor setup that includes collaboration features. We highly recommend you get your setup ready based on the comments feature integration guide before moving any further.

# Views for comments and suggestions

The view classes used by default by CKEditor 5 collaboration features are:

These closed-source classes are exported by the ckeditor5-premium-features package on npm (learn more).

It is highly recommended to get familiar with the comment view, comment thread view, and the suggestion view API documentation before continuing.

# Changing the view template

To use a custom view template, you need to:

  1. Create a new view class by extending the default view class.
  2. Provide (or extend) the template by overwriting the getTemplate() method.
  3. Set your custom view class through the editor configuration. We recommend setting it in the default configuration.

Creating a custom view:

// ...

import { CommentView } from 'ckeditor5-premium-features';

// Create a new comment view class basing on the default view.
class MyCommentView extends CommentView {
    // Overwrite the method to provide a custom template.
    getTemplate() {
        return {
            // Provide the template definition here.
            // ...
        };
    }
}

// ...

Using the custom view in the editor configuration:

// ...

const editorConfig = {
    // ...

    comments: {
        CommentView: MyCommentView
    },

    // ...
};

ClassicEditor.create(document.querySelector('#editor'), editorConfig);

Custom comment and suggestion thread views can be set in a similar way:

// ...

const editorConfig = {
    // ...

    comments: {
        CommentThreadView: MyCommentThreadView,
    },

    trackChanges: {
        SuggestionThreadView: MySuggestionThreadView
    },

    // ...
};

ClassicEditor.create(document.querySelector('#editor'), editorConfig);

# Example: Adding a new button

Below is an example of how you can provide a simple custom feature that requires creating a new UI element and additional styling.

The proposed feature should allow for marking some comments as important. An important comment should have a yellow border on the right.

To bring this feature, you need:

  • A CSS class to change the border of an important comment.
  • A button to toggle the comment model state.
  • An integration between the model state and the view template.

# Styling for an important comment

This step is easy. Add the following styles to the style.css file in your project:

/* style.css */

/* Yellow border for an important comment. */
.ck-comment--important {
    border-right: 3px solid hsl( 55, 98%, 48% );
}

# Creating a button and adding it to the template

In this step, you will add some new elements to the template.

Keep in mind that you do not need to overwrite the whole template. You can extend it instead.

// main.js

// ...

import { CommentView } from 'ckeditor5-premium-features';
import { ButtonView } from 'ckeditor5';

export default class ImportantCommentView extends CommentView {
    constructor( ...args ) {
        super( ...args );

        // Bind `isImportant` value to comment model custom attributes.
        this.bind( 'isImportant' ).to( this._model, 'attributes', attributes => !!attributes.isImportant );
    }

    getTemplate() {
        // Use the original method to get the default template.
        // The default template definition structure is described in the comment view API.
        const templateDefinition = super.getTemplate();

        // If `isImportant` is `true`, add the `ck-comment--important` class to the template.
        templateDefinition.children[ 0 ].attributes.class.push( this.bindTemplate.if( 'isImportant', 'ck-comment--important' ) );

        // Add the new button next to the other comment buttons (edit and remove).
        templateDefinition.children[ 0 ].children[ 1 ].children[ 1 ].children.add( this._createImportantButtonView(), 0 );

        return templateDefinition;
    }

    _createImportantButtonView() {
        // Create a new button view.
        const button = new ButtonView( this.locale );

        // Create an icon for the button view.
        const starIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M15.22 18.36c.18-.02.35-.1.46-.25a.6.6 0 00.11-.5l-1.12-5.32 4.12-3.66a.6.6 0 00.18-.65.63.63 0 00-.54-.42l-5.54-.6L10.58 2a.64.64 0 00-.58-.37.64.64 0 00-.58.37l-2.3 4.94-5.55.6a.63.63 0 00-.54.43.6.6 0 00.18.65l4.12 3.66-1.12 5.32c-.05.24.04.49.25.63.2.14.47.16.68.04L10 15.59l4.86 2.69c.1.06.23.09.36.08zm-.96-1.83l-3.95-2.19a.65.65 0 00-.62 0l-3.95 2.19.91-4.33a.6.6 0 00-.2-.58L3.1 8.64l4.51-.5a.64.64 0 00.51-.36L10 3.76l1.88 4.02c.09.2.28.34.5.36l4.52.5-3.35 2.98a.6.6 0 00-.2.58l.91 4.33z"/></svg>';

        // Use the localization service.
        // The feature will be translatable.
        const t = this.locale.t;

        // Set the label and the icon for the button.
        button.set( {
            icon: starIcon,
            isToggleable: true,
            tooltip: t( 'Important' ),
            withText: true
        } );

        // Add a class to the button to style it.
        button.extendTemplate( {
            attributes: {
                class: 'ck-button-important'
            }
        } );

        // The button should be enabled if the comment model is not in the read-only mode.
        // The same setting is used for other comment buttons.
        button.bind( 'isEnabled' ).to( this._model, 'isReadOnly', value => !value );

        // The button should be hidden if the comment is not editable
        // (this is true when the current user is not the comment author).
        // The same setting is used for the other comment buttons.
        button.bind( 'isVisible' ).to( this._model, 'isEditable' );

        // When the button is clicked, change the comment state in the comment model.
        // The `attributes.isImportant` value will be available together with other comment data.
        button.on( 'execute', () => {
            this._model.setAttribute( 'isImportant', !this._model.attributes.isImportant );
        } );

        return button;
    }
}

// ...

# Enabling the custom view

The custom view will be enabled in editor configuration, as shown earlier.

// main.js

// ...

const editorConfig = {
    // ...

    comments: {
        CommentView: ImportantCommentView,
        editorConfig: {
            plugins: [ Essentials, Paragraph, Bold, Italic ]
        }
    }
};

ClassicEditor.create(document.querySelector('#editor'), editorConfig);

# Live demo