Bootstrap rich text editor: set up Summernote without mixing Bootstrap versions

A Bootstrap textarea displays the tags you type because Bootstrap styles form controls; it does not turn them into a rich text editor. Summernote adds that editing layer. Choose its Bootstrap integration before loading assets, then check the editor's HTML when you submit the form.

For an existing Bootstrap 4 admin page, use Summernote's summernote-bs4 stylesheet and script together. Its getting-started page documents the Bootstrap 4 route and a separate Lite route. A file named bs4 is not evidence of Bootstrap 5 compatibility. If your application uses Bootstrap 5, establish a supported integration for that stack before copying this configuration.

Load one coherent set of assets

On the Bootstrap 4 route, load jQuery, the Bootstrap dependencies, and the matching Summernote assets in the documented order. Keep the versions in your package lock or asset manifest. Loading a second jQuery copy after the plugin can leave the page's current $ without summernote attached.

Start with a simple announcement outside a modal:

<form id="announcement-form">
  <label for="announcement">Building announcement</label>
  <textarea id="announcement" name="announcement"><p>The loading bay closes at 4 p.m.</p></textarea>
  <button type="submit">Check announcement</button>
  <p id="announcement-message" role="status"></p>
</form>
<pre id="captured-html"></pre>

After the required scripts have loaded, initialize the control once:

const $announcement = $('#announcement');

$announcement.summernote({
  height: 180,
  toolbar: [
    ['style', ['bold', 'italic']],
    ['para', ['ul', 'ol']],
    ['insert', ['link']]
  ]
});

The restricted toolbar keeps this announcement short. It is a set of editing controls, not a security policy for all pasted or saved HTML. Use the application's existing HTML validation and sanitization at its storage and rendering boundaries.

Capture the edited announcement, including an honest empty state

An empty Summernote editor can contain <p><br></p> so the caret has somewhere to go. Checking html.length treats that markup as content. Use Summernote's documented empty check for this editor state, and add any application rule about whitespace or required words separately. Its API and options reference covers both isEmpty and code.

document.querySelector('#announcement-form').addEventListener('submit', event => {
  event.preventDefault();
  const message = document.querySelector('#announcement-message');

  if ($announcement.summernote('isEmpty')) {
    message.textContent = 'Enter an announcement before continuing.';
    return;
  }

  const html = $announcement.summernote('code');
  document.querySelector('#captured-html').textContent = html;
  message.textContent = 'Current HTML captured locally. Nothing has been saved.';
});

Bold “4 p.m.”, capture the announcement, and inspect the output. The captured source should contain the edited words and their formatting. Clearing the editor should produce the empty message. The pre uses textContent so inspecting the HTML does not execute it.

The textarea guide explains how a native textarea's current value differs from its initial markup. Once a rich editor wraps it, use the library's documented extraction path for the value you are checking.

Move into a modal after the simple page works

Summernote documents dialogsInBody: true for an editor inside a modal. Put it in the initialization options before testing the link dialog. This changes where Summernote creates its dialogs; it does not remove the need to check focus and stacking in your application's actual modal.

Bootstrap 4's modal documentation describes its focus and positioning behavior. Do not solve a focus problem by disabling keyboard containment across the whole application. Test opening the editor's link dialog, entering a URL, returning to the announcement, and closing the parent modal with the keyboard.

If the toolbar is absent, inspect failed asset requests and the plugin's initialization error first. If the toolbar works but a dialog appears behind the modal, inspect the dialog container and stacking context. Keeping these cases separate prevents a working editor from being reinitialized each time the modal opens.

Before connecting the capture step to a real save, change the closing time, save through your application, and reopen the record. That reopened announcement is the evidence that the new value reached storage.

Keep reading