Creating custom Toggle toolbar buttons

A toggle button triggers an action when clicked but also has a concept of state. This means it can be toggled on and off. A toggle button gives the user visual feedback for its state through CSS styling. An example of this behavior is the Bold button that is highlighted when the cursor is in a word with bold formatting.

Options

Name Value Requirement Description

text

string

optional

Text to display if no icon is found.

icon

string

optional

Name of the icon to be displayed. Must correspond to an icon: in the icon pack, in a custom icon pack, or added using the addIcon API.

tooltip

string

optional

Text for button tooltip.

enabled

boolean

optional

default: true - Represents the button’s state. When false, the button is unclickable. Toggled by the button’s API.

active

boolean

optional

default: false - Represents the button’s state. When true, the button is highlighted. Toggled by the button’s API.

onSetup

(api) => (api) => void

optional

default: () => () => {} - Function invoked when the button is rendered. For details, see: Using onSetup.

onAction

(api) => void

required

Function invoked when the button is clicked.

API

Name Value Description

isEnabled

() => boolean

Checks if the button is enabled.

setEnabled

(state: boolean) => void

Sets the button’s enabled state.

isActive

() => boolean

Checks if the button is on.

setActive

(state: boolean) => void

Sets the button’s toggle state.

NOTE: This feature is only available for TinyMCE 6.4 and later.

setText

(text: string) => void

Sets the text label to display.

setIcon

(icon: string) => void

Sets the icon of the button.

Toggle button example and explanation

  • TinyMCE

  • HTML

  • JS

  • Edit on CodePen

<textarea id="custom-toolbar-toggle-button">
  <p><img style="display: block; margin-left: auto; margin-right: auto;" title="Tiny Logo" src="https://www.tiny.cloud/docs/images/logos/android-chrome-256x256.png" alt="TinyMCE Logo" width="128" height="128"></p>
  <h2 style="text-align: center;">Welcome to the TinyMCE editor demo!</h2>
  <p>Select a menu item from the listbox above and it will insert contents into the editor at the caret position.</p>

  <h2>Got questions or need help?</h2>
  <ul>
    <li>Our <a href="https://www.tiny.cloud/docs/tinymce/6/">documentation</a> is a great resource for learning how to configure TinyMCE.</li>
    <li>Have a specific question? Try the <a href="https://stackoverflow.com/questions/tagged/tinymce" target="_blank" rel="noopener"><code>tinymce</code> tag at Stack Overflow</a>.</li>
    <li>We also offer enterprise grade support as part of <a href="https://www.tiny.cloud/pricing">TinyMCE premium plans</a>.</li>
  </ul>

  <h2>Found a bug?</h2>
  <p>If you think you have found a bug please create an issue on the <a href="https://github.com/tinymce/tinymce/issues">GitHub repo</a> to report it to the developers.</p>

  <h2>Finally ...</h2>
  <p>Don't forget to check out our other product <a href="http://www.plupload.com" target="_blank">Plupload</a>, your ultimate upload solution featuring HTML5 upload support.</p>
  <p>Thanks for supporting TinyMCE! We hope it helps you and your users create great content.
    <br>All the best from the TinyMCE team.</p>
</textarea>
tinymce.init({
  selector: 'textarea#custom-toolbar-toggle-button',
  toolbar: 'customStrikethrough customToggleStrikethrough',
  setup: (editor) => {
    editor.ui.registry.addToggleButton('customStrikethrough', {
      text: 'Strikethrough',
      onAction: (api) => {
        editor.execCommand('mceToggleFormat', false, 'strikethrough');
        api.setActive(!api.isActive());
      }
    });

    editor.ui.registry.addToggleButton('customToggleStrikethrough', {
      icon: 'strike-through',
      onAction: (_) => editor.execCommand('mceToggleFormat', false, 'strikethrough'),
      onSetup: (api) => {
        api.setActive(editor.formatter.match('strikethrough'));
        const changed = editor.formatter.formatChanged('strikethrough', (state) => api.setActive(state));
        return () => changed.unbind();
      }
    });
  },
  content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:16px }'
});

The example above adds two custom strikethrough buttons with the same onAction configuration. The configuration uses editor.execCommand(command, ui, args) to execute mceToggleFormat. This editor method toggles the specified format on and off, but only works for formats that are already registered with the editor. In this example, strikethrough is the registered format.

The first button applies and removes strikethrough formatting, and its state toggles on click using api.setActive(!api.isActive()). However, the expected behavior is that the button’s state will reflect whether the selected content has strikethrough formatting. For example, if the cursor is moved into editor content that has strikethrough formatting the button will become active and if it is moved into content that does not have strikethrough formatting the button will become inactive. The first button in the example does not do this, since its state only toggles when the button is clicked.

To achieve this, the second button uses onSetup to register a callback for strikethrough content using editor.formatter.formatChanged(formatName, callback). This method executes the specified callback function when the selected content has the specified formatting.

The format name given to mceToggleFormat via editor.execCommand(command, ui, args) and to editor.formatter.formatChanged(formatName, callback) is the same.

The callback given to editor.formatter.formatChanged is a function that takes a state boolean representing whether the currently selected content contains the applied format. This state boolean is used to set the button’s active state to match if the selected content has the specified formatting by using api.setActive(state) from the button’s API. This ensures the customToggleStrikethrough button is only active when the selected content contains the strikethrough formatting.

For formats that require variables, the editor.formatter.formatChanged function takes two extra arguments: similar and vars.

When the similar argument is true, similar formats will all be treated as the same by formatChanged. Similar formats are those with the same formatName but different variables. This argument will default to false.

The vars argument controls which variables are used to match the content when determining whether to run the callback. This argument is only used when similar is false.

Using onSetup

onSetup is a complex property. It takes a function that is passed the component’s API and should return a callback that is passed the component’s API and returns nothing. This occurs because onSetup runs whenever the component is rendered, and the returned callback is executed when the component is destroyed. This is essentially an onTeardown handler, and can be used to unbind events and callbacks.

To clarify, in code onSetup may look like this:

onSetup: (api) => {
  // Do something here on component render, like set component properties or bind an event listener

  return (api) => {
    // Do something here on teardown, like unbind an event listener
  };
};

To bind a callback function to an editor event use editor.on(eventName, callback). To unbind an event listener use editor.off(eventName, callback). Any event listeners should be unbound in the teardown callback. The only editor event which does not need to be unbound is init e.g. editor.on('init', callback).

  • The callback function for editor.off() should be the same function passed to editor.on(). For example, if a editorEventCallback function is bound to the NodeChange event when the button is created, onSetup should return (api) => editor.off('NodeChange', editorEventCallback).

  • If onSetup does not have any event listeners or only listens to the init event, onSetup can return an empty function e.g. return () => {};.