Integrate Froala WYSIWYG Editor into Svelte
Posted on By Mostafa Yousef | In General,
Table of contents
- Key Takeaways
- Prerequisites
- Step 1: Install the SDK
- Step 2: Render the Editor and Wire Up Two-Way Binding
- Step 3: Configure the Editor with the
configProp - Step 4: Handle Editor Events
- Step 5: Initialize on Special Tags with the
tagProp - Step 6: Control Initialization Manually with the
manualProp - Step 7: Drive the Editor Programmatically with the
editorProp - Conclusion
- FAQ
Adding a rich text editor to a Svelte app used to mean writing your own wrapper: mounting Froala in an onMount, tearing it down in onDestroy, and manually piping content in and out of your component state. Froala Editor v5.3.0 removes that work. The official svelte-froala-wysiwyg package gives you a real Svelte component with two-way binding, full access to Froala’s options and events, and direct access to the underlying editor instance.
This guide walks through installing the SDK, rendering the editor, wiring up two-way binding, configuring the toolbar, handling events, initializing on special tags, controlling the editor manually, and driving it programmatically through the editor instance. Every prop is explained, and every code sample is copy-paste ready.
Key Takeaways
- The
svelte-froala-wysiwygcomponent ships with Froala v5.3.0 and replaces hand-rolled Svelte integrations. - Two-way binding through
bind:modelkeeps your editor content and a Svelte variable in sync with no extra glue code. - The
configprop accepts the full Froala options object, includingevents, so you configure everything in one place. bind:editorhands you the live editor instance so you can call any of Froala’s methods directly from your component.
Prerequisites
- Node.js and npm installed.
- A Svelte project (this guide uses Svelte 5, the current default). The wrapper also works with Svelte 3 and 4.
- A Froala Editor license key for production. You can start with a free trial key.
Step 1: Install the SDK
Install the wrapper from npm:
npm install svelte-froala-wysiwyg The froala-editor core package is a dependency of the wrapper, so you do not need to install it separately. That core package is where the editor’s JavaScript and CSS live, and you will import the CSS from it in the next step.
Step 2: Render the Editor and Wire Up Two-Way Binding
Here is the smallest working setup. Drop this into any .svelte file, for example src/App.svelte in a plain Vite + Svelte project.
<script>
// Import the Froala wrapper component.
import FroalaEditor from "svelte-froala-wysiwyg";
// Load Froala's editor UI styles (toolbar, buttons, popups).
import 'froala-editor/css/froala_editor.pkgd.min.css';
// Load Froala's content styles (applied to the text you type).
import 'froala-editor/css/froala_style.css';
// Reactive state holding the editor's HTML.
// $state() makes this a Svelte 5 rune so bind:model can update it.
let htmlContent = $state("<p>Hello, Froala!</p>");
</script>
<!--
bind:model creates a two-way link between htmlContent and the editor.
Type in the editor and htmlContent updates. Change htmlContent in code
and the editor updates. No event listeners required.
-->
<FroalaEditor bind:model={htmlContent} />
<!-- A live preview to prove the binding works. -->
<div class="preview">
{@html htmlContent}
</div>
A few things worth explaining here, because they trip people up:
Why $state()? In Svelte 5, a plain let htmlContent = "..." is not reactive enough to be a two-way binding target. Wrapping the initial value in the $state() rune tells Svelte to track it. If you are on Svelte 3 or 4, use a plain let instead, exactly as the official docs show. The bind: directive itself works the same across all three versions, so the <FroalaEditor> line does not change.
Why {@html htmlContent}? Svelte escapes interpolated strings by default to prevent XSS. The editor produces real HTML, so you need {@html ...} to render it as markup rather than as escaped text. Only do this with content you trust or have sanitized. Rendering unsanitized user HTML is a security risk, which is a good reason to sanitize on your server before storing or re-displaying it.
Play with our interactive demo.
Step 3: Configure the Editor with the config Prop
Every Froala option goes through a single config prop, which takes a plain object. This is where you set the license key, the toolbar, the placeholder, and anything else from the Froala options reference.
<script>
import FroalaEditor from "svelte-froala-wysiwyg";
import 'froala-editor/css/froala_editor.pkgd.min.css';
import 'froala-editor/css/froala_style.css';
let htmlContent = $state("");
const myConfig = {
// Your Froala license key. Standard across all Froala SDKs.
// Omit during local trials; required for production.
key: 'YOUR_FROALA_LICENSE_KEY',
// Grey prompt text shown when the editor is empty.
placeholderText: 'Start typing...',
// Turn off the character counter in the bottom-right corner.
charCounterCount: false,
// Choose exactly which toolbar buttons appear, and in what order.
toolbarButtons: ['bold', 'italic', 'underline']
};
</script>
<FroalaEditor config={myConfig} bind:model={htmlContent} />
Breaking down each option:
keyis your license key.placeholderTextsets the hint text shown in an empty editor. Set it to an empty string to hide the placeholder entirely.charCounterCounttoggles the character count display. It istrueby default; set it tofalseto hide it.toolbarButtonsis an array of button command names, rendered left to right in the order you list them. Anything you leave out simply does not appear. The full list of available commands lives in the options documentation.
Defining configuration as a separate const keeps your markup clean and makes the config easy to reuse or test. Declare it outside any reactive block so it is created once rather than rebuilt on every render.
Step 4: Handle Editor Events
Froala events are not separate props. They live inside a nested events object on your config. Each key is an event name and each value is the handler function.
<script>
import FroalaEditor from "svelte-froala-wysiwyg";
import 'froala-editor/css/froala_editor.pkgd.min.css';
import 'froala-editor/css/froala_style.css';
const config = {
events: {
// Fires whenever the content changes (typing, paste, formatting).
'contentChanged': function () {
console.log('Content updated!');
// Inside a handler, `this` is the editor instance,
// so you can call `this.html.get()` to read the current HTML.
},
// Fires when the editor gains focus.
'focus': function () {
console.log('Editor focused');
}
}
};
</script>
<FroalaEditor config={config} /> Two useful details:
this is the editor instance. Because these are Froala events, this inside each handler refers to the live editor. That means this.html.get() reads the current content, this.selection.text() reads the selected text, and so on. This only works with a regular function () {}. An arrow function would rebind this and break that access, so use regular functions here.
contentChanged versus bind:model. If all you need is the current HTML in a variable, bind:model already handles it. Reach for contentChanged when you want to react to edits. The full event list is in the events documentation.
Step 5: Initialize on Special Tags with the tag Prop
Froala does not have to live in a div. The tag prop lets you attach the editor to an <img>, <button>, <input>, or <a> element. This is how you get Froala’s inline image or link editing tools around an existing element.
When you use tag, the meaning of model changes. Instead of an HTML string, model becomes an object of the element’s attributes.
<script>
import FroalaEditor from "svelte-froala-wysiwyg";
import 'froala-editor/css/froala_editor.pkgd.min.css';
import 'froala-editor/css/froala_style.css';
// For a tag editor, model holds the element's attributes,
// not an HTML string.
let imageModel = $state({
src: "https://froala.com/assets/img/logo.png",
alt: "Froala Logo"
});
</script>
<!-- tag="img" mounts Froala's image tools on an <img> element. -->
<FroalaEditor tag="img" bind:model={imageModel} /> tagnames the element type to initialize on. Valid values areimg,button,input, anda. Omit it and the editor defaults to a standard rich textdiv.modelin this mode maps to the element’s attributes. Editing the image through Froala updates the bound object, and updating the object in your code updates the element. ReadimageModel.srcto get the current source after a user swaps or resizes the image.
Step 6: Control Initialization Manually with the manual Prop
By default the editor initializes as soon as the component mounts. Sometimes you want to delay that, for example until data loads or a user clicks a button. Set manual={true} and trigger initialization yourself.
To call the component’s methods, grab a reference to it with Svelte’s bind:this.
<script>
import FroalaEditor from "svelte-froala-wysiwyg";
import 'froala-editor/css/froala_editor.pkgd.min.css';
import 'froala-editor/css/froala_style.css';
// Holds a reference to the FroalaEditor component instance.
let editorRef;
function init() {
// Kick off initialization on demand.
editorRef.initializeEditor();
}
</script>
<!-- In Svelte 5, use onclick (not on:click). -->
<button onclick={init}>Initialize Editor</button>
<FroalaEditor
bind:this={editorRef}
manual={true}
model="<p>Wait for it...</p>"
/> manualset totruestops the editor from auto-initializing. It stays dormant until you callinitializeEditor().bind:this={editorRef}stores the component instance ineditorRefso you can call its methods. This is standard Svelte and works the same in every version.onclickis the Svelte 5 event syntax. On Svelte 3 or 4, useon:click={init}as the official docs show.
The component exposes three methods for manual control: initializeEditor() to start it, destroy() to tear it down and return the element to its original state, and getEditor() to retrieve the underlying Froala instance.
Step 7: Drive the Editor Programmatically with the editor Prop
bind:model gives you the content as a string. Sometimes you need the whole editor, not just its text: insert HTML at the cursor, read the current selection, toggle formatting, or save an undo step. That is what the editor prop is for.
Binding to editor exposes the live Froala editor instance, so you can drive it with any of Froala’s methods. Instead of receiving the instance through a callback, you bind:editor to pull it into your own variable.
<script>
import FroalaEditor from "svelte-froala-wysiwyg";
import 'froala-editor/css/froala_editor.pkgd.min.css';
import 'froala-editor/css/froala_style.css';
let htmlContent = $state("<p>Start here.</p>");
// Holds the live Froala editor instance once it is ready.
let editorInstance = $state();
function insertSignature() {
// Guard against calling methods before the editor exists.
if (!editorInstance) return;
// Insert HTML at the current cursor position.
editorInstance.html.insert('<p>— Sent from my Svelte app</p>');
// Record an undo step so this insertion can be reverted with Ctrl+Z.
editorInstance.undo.saveStep();
}
function logSelection() {
if (!editorInstance) return;
// Read whatever text the user currently has selected.
console.log('Selected text:', editorInstance.selection.text());
}
</script>
<button onclick={insertSignature}>Insert Signature</button>
<button onclick={logSelection}>Log Selection</button>
<!--
bind:model -> two-way string binding for the content
bind:editor -> the live instance for calling methods
-->
<FroalaEditor bind:model={htmlContent} bind:editor={editorInstance} /> The mental model that keeps these two props straight:
- Use
bind:modelwhen you want the content. It is a synced HTML string. Reach for it to save, preview, or restore what the user wrote. - Use
bind:editorwhen you want to command the editor. It is the full instance and its entire method API. Reach for it to insert at the cursor, read or manipulate the selection, toggle formatting, or trigger undo steps.
The two work together. In the example above, model tracks the content while editor performs the actions.
Conclusion
The official svelte-froala-wysiwyg SDK turns Froala into a first-class Svelte component. You render <FroalaEditor>, bind model for content, pass a config object for everything else, and bind editor when you need to command the editor directly. No lifecycle boilerplate, no manual DOM teardown.
For the complete prop and method reference, see the official Svelte documentation. For the full set of configuration options, browse the options reference, and for everything you can do through the editor instance, see the methods reference.
Ready to build? Install the package, get a license key, drop in the basic example above, and you will have a working editor in under five minutes. The source is on GitHub if you want to dig deeper or file an issue.
FAQ
Do I need to install froala-editor separately?
No. It comes in as a dependency of svelte-froala-wysiwyg. You do need to import its CSS files yourself, since bundlers do not include CSS automatically.
Where do I put my license key?
Inside the config object as key: 'YOUR_KEY'. This is the standard placement across Froala’s framework SDKs.
How do I load Froala plugins like tables or image upload?
Import the plugin’s file from froala-editor/js/plugins/, for example import 'froala-editor/js/plugins/table.min.js';, then add its buttons to toolbarButtons. Import only what you use to keep your bundle small.
What is the difference between bind:model and bind:editor?
bind:model gives you the content as a synced HTML string. bind:editor gives you the live editor instance and its full method API. Use model to read or set content; use editor to command the editor, such as inserting at the cursor or reading the selection.
Does two-way binding work the same in Svelte 5 and older versions?
The bind: directive on the component is the same across versions. What changes is your variable: use $state() in Svelte 5, and a plain let in Svelte 3 or 4.
- Whats on this page hide
No comment yet, add your voice below!