Introduction

Edra is a component based rich text editor built with Tiptap and Svelte. It is designed to be highly customizable and extensible, allowing developers to create their own custom editors with ease.

The main goal of Edra is to provide a baseline with minimum to no pre-configured components, yet highly customizable and extensible.

Note:

Why is edra a component based library?

The main reason for this library is to provide the full control over the editor, allowing developers to create their own custom editors with ease. This is achieved by using the powerful Tiptap library, which provides a rich set of features and customization options. This way the developers have the full control over the code and they can achieve anything with it.


Installation

Edra comes with two ui floavors, headless and shadcn. The headless ui usage pure css with classes and the shadcn ui usage the svelte-shadcn.

Choose which one you want to use.

  1. Installation with headless ui
  2. Installation with shadcn ui

Installation with headless ui

For installation with headless ui, you can use the following command:

# For NPM
npx edra init headless
# For PNPM
pnpm dlx edra init headless

The above command will copy the edra component as folder in src/lib/components/edra and install the required dependencies.


Installation with shadcn ui

Warning:

The shadcn ui flavor have some prerequisites, so please read the following instructions carefully.

Prerequisites

The shacn ui based components usages the svelte-shadcn and tailwind-typography packages. You can find their respective installation guides in their documentation.

After installing the svelte-shadcn and tailwind-typography packages, you need to add the following svelte-shadcn components in your project.

# For NPM
npx shadcn-svelte@next add dropdown-menu button tooltip input popover
# using pnpm
pnpm dlx shadcn-svelte@next add dropdown-menu button tooltip input popover

After the installation of svelte-shadcn components, we can run following command to install edra component with shadcn ui.

# For NPM
npx edra init shadcn
# For PNPM
pnpm dlx edra init shadcn

This command will copy the edra component as folder in src/lib/components/edra and install the required dependencies.


Usage

The Edra library and its two primary components, Edra and EdraToolbar. Edra is the main component that will render the editor. EdraToolbar is used to render the toolbar.

Structure

Editor <Edra>

The <Edra> component is the core rich-text editor built on Tiptap. It initializes the editor with various extensions (such as code block highlighting, media placeholders, math rendering, etc.) and manages editor lifecycle events. It also supports advanced features such as a bubble menu (contextual toolbar) and slash commands.

/**
 * Props for Edra's editor component
 */
export interface EdraProps {
	class?: string;
	content?: Content;
	editable?: boolean;
	limit?: number;
	editor?: Editor;
	showSlashCommands?: boolean;
	showLinkBubbleMenu?: boolean;
	showTableBubbleMenu?: boolean;
	/**
	 * Callback function to be called when the content is updated
	 * @param content
	 */
	onUpdate?: (props: { editor: Editor; transaction: Transaction }) => void;
	children?: Snippet<[]>;
}

Properties:

  • class?: string Additional classes to style the editor container.
  • content?: Content The initial content for the editor.
  • editable?: boolean Flag indicating if the editor should be editable (true) or read-only (false).
  • limit?: number Sets the maximum number of characters allowed in the editor.
  • editor?: Editor Bindable property to pass the editor instance to the Edra component.
  • showSlashCommands?: boolean (default: true) Enables slash commands in the editor. Slash commands can be triggered by typing / in the editor.
  • showLinkBubbleMenu?: boolean (default: true) Enables a bubble menu for editing links. This menu appears when the user clicks on a link in the editor.
  • showTableBubbleMenu?: boolean (default: true) Enables a bubble menu for table editing. This menu appears when interacting with table rows or columns in the editor.
  • onUpdate? To update the content of the editor after a transaction is executed
  • children? Pass children components to the editor.

Toolbar <EdraToolbar>

The <EdraToolbar> component renders the editor’s toolbar and displays a set of command buttons in a specific order. You can control which commands appear and in what order by passing an array to the allowedCommands property. The toolbar supports “special” toolbar components like font size, quick color, and search/replace.

interface Props {
	class?: string;
	editor: Editor;
	children?: Snippet;
}

Properties:

  • class?: string Additional classes to style the editor container.
  • editor?: Editor Bindable property to pass the editor instance to the Edra component.
  • children? Allows for components to be passed directly to the toolbar (e.g., <FontSize {editor} />).

Warning:

If children components are passed to the <EdraToolbar> it will override the default set of toolbar commands.

BubbleMenu <EdraBubbleMenu {editor} />

The <EdraBubbleMenu> component provides a floating formatting toolbar that appears when the user selects text within the editor. It gives quick access to inline formatting options like bold, italic, and code, and can be extended with custom components or actions using children.

interface Props {
	class?: string;
	editor: Editor;
	children?: Snippet<[]>;
}

Properties:

  • class?: string Additional classes to style the editor container.
  • editor?: Editor Bindable property to pass the editor instance to the Edra component.
  • children? Allows for components to be passed directly to the bubble menu.

Warning:

If children components are passed to the <EdraBubbleMenu> it will override the default set of toolbar commands.


Examples

Below are example use cases for Shadcn and Headless:

Shadcn Basic Example

A simple setup using the default Edra editor, toolbar, and bubble menu.

<script lang="ts">
	import type { Content, Editor } from '@tiptap/core';
	import type { Transaction } from '@tiptap/pm/state';
	import { Edra, EdraToolbar, EdraBubbleMenu } from '$lib/edra/shadcn/index.js';

	// Editor states
	let content = $state<Content>();
	let editor = $state<Editor>();
	let showToolBar = $state(true);

	function onUpdate(props: { editor: Editor; transaction: Transaction }) {
		content = props.editor.getJSON();
	}
</script>

<div class="py-4 text-center text-xl font-bold">Shadcn Example</div>
<div class="w-7xl mx-auto px-4">
	{#if editor && showToolBar}
		<div class="rounded-t border-x border-t p-1 overflow-auto">
			<EdraToolbar {editor} />
		</div>
		<EdraBubbleMenu {editor} />
	{/if}
	<div class="rounded-b border">
		<Edra class="h-64 overflow-auto" bind:editor {content} {onUpdate} />
	</div>
</div>

Shadcn Advanced Example

A more detailed implementation showcasing toolbar customization and additional feature toggles like slash commands and table tools.

<script lang="ts">
	import type { Content, Editor } from '@tiptap/core';
	import type { Transaction } from '@tiptap/pm/state';
	import { Edra, EdraToolbar, EdraBubbleMenu } from '$lib/edra/shadcn/index.js';
	import FontSize from '$lib/edra/shadcn/components/FontSize.svelte';

	// Editor states
	let content = $state<Content>();
	let editor = $state<Editor>();
	let showSlashCommands = $state(true);
	let showLinkBubbleMenu = $state(true);
	let showTableBubbleMenu = $state(true);

	function onUpdate(props: { editor: Editor; transaction: Transaction }) {
		content = props.editor.getJSON();
	}
</script>

<div class="py-4 text-center text-xl font-bold">Shadcn Example</div>
<div class="w-7xl mx-auto px-4">
	{#if editor}
		<div class="rounded-t border-x border-t p-1">
			<!-- Default Edra toolbar -->
			<EdraToolbar {editor} />

			<!-- Customized Edra toolbar -->
			<EdraToolbar {editor}>
				<div class="border-r px-3 text-sm">Customized toolbar</div>
				<FontSize {editor} />
			</EdraToolbar>
		</div>
		<!-- Add bubble menu -->
		<EdraBubbleMenu {editor} />
	{/if}
	<div class="rounded-b border">
		<Edra
			class="h-64 overflow-auto"
			bind:editor
			{content}
			{showSlashCommands}
			{showLinkBubbleMenu}
			{showTableBubbleMenu}
			{onUpdate}
		/>
	</div>
</div>

Headless Basic Example

A simple setup using the default Edra editor, toolbar, and bubble menu.

<script lang="ts">
	import type { Content, Editor } from '@tiptap/core';
	import type { Transaction } from '@tiptap/pm/state';
	import { Edra, EdraToolbar, EdraBubbleMenu } from '$lib/edra/headless/index.js';

	// Editor states
	let content = $state<Content>();
	let editor = $state<Editor>();

	function onUpdate(props: { editor: Editor; transaction: Transaction }) {
		content = props.editor.getJSON();
	}
</script>

<div class="py-4 text-center text-xl font-bold">Headless Example</div>
<div class="w-7xl mx-auto px-4">
	{#if editor}
		<div class="rounded-t border-x border-t p-1">
			<EdraToolbar {editor} />
		</div>
		<EdraBubbleMenu {editor} />
	{/if}
	<div class="rounded-b border">
		<Edra class="h-64 overflow-auto" bind:editor {content} {onUpdate} />
	</div>
</div>

Headless Advanced Example

A more detailed implementation showcasing toolbar customization and additional feature toggles like slash commands and table tools.

<script lang="ts">
	import type { Content, Editor } from '@tiptap/core';
	import type { Transaction } from '@tiptap/pm/state';
	import { Edra, EdraToolbar, EdraBubbleMenu } from '$lib/edra/headless/index.js';
	import EdraToolBarIcon from '$lib/edra/headless/components/EdraToolBarIcon.svelte';
	import { isMac } from '$lib/edra/utils.js';

	// Editor states
	let content = $state<Content>();
	let editor = $state<Editor>();
	let showSlashCommands = $state(true);
	let showLinkBubbleMenu = $state(true);
	let showTableBubbleMenu = $state(true);

	function onUpdate(props: { editor: Editor; transaction: Transaction }) {
		content = props.editor.getJSON();
	}
</script>

<div class="py-4 text-center text-xl font-bold">Headless Example</div>
<div class="w-7xl mx-auto px-4">
	{#if editor}
		<div class="rounded-t border-x border-t p-1">
			<!-- Default Edra toolbar -->
			<EdraToolbar {editor} />

			<!-- Customized Edra toolbar -->
			<EdraToolbar {editor}>
				<div class="border-r px-3 text-sm">Customized toolbar</div>
				<EdraToolBarIcon
					command={{
						iconName: 'Bold',
						name: 'bold',
						label: 'Bold',
						shortCuts: [`${isMac ? 'Cmd' : 'Ctrl'}+B`],
						action: (editor) => {
							editor.chain().focus().toggleBold().run();
						}
					}}
					{editor}
				/>
			</EdraToolbar>
		</div>
		<!-- Add bubble menu -->
		<EdraBubbleMenu {editor} />
	{/if}
	<div class="rounded-b border">
		<Edra
			class="h-64 overflow-auto"
			bind:editor
			{content}
			{showSlashCommands}
			{showLinkBubbleMenu}
			{showTableBubbleMenu}
			{onUpdate}
		/>
	</div>
</div>

Warning:

You must need to create an editor instance, if you want to use EdraToolbar and/or EdraBubbleMenu component.

You can also use the class names for the Edra component to customize the editor’s appearance. e.g.

<EdraToolbar {editor} />
<EdraToolbar class="w-full border-b p-1" {editor} />

<Edra bind:editor {showMenu} {content} {onUpdate} />
<Edra class="overflow-auto" bind:editor {showMenu} {content} {onUpdate} />

<style>
	:global(.edra-toolbar) {
		width: 100%;
		border-bottom-width: 1px;
		padding: 0.25rem;
	}
	:global(.edra) {
		overflow: auto;
	}
</style>

Features

JSON Output

You can controll the editor output without worrying about the initial content format. The simplest way to do this is to use the onUpdate prop, which is a callback function that is called when the content is updated.

For the JSON output, you can use following example code:

function onUpdate(props: { editor: Editor; transaction: Transaction }) {
	const myOutput = props.editor.getJSON();
	// save the output in your preferred way
	saveContent(myOutput);
}

HTML Output

Similar to JSON output, you can also get the HTML output of the editor in onUpdate callback function.

function onUpdate(props: { editor: Editor; transaction: Transaction }) {
	const myOutput = props.editor.getHTML();
	// save the output in your preferred way
	saveContent(myOutput);
}

Slash Command

The SlashCommand is a feature that allows you to add custom commands to the editor. It is a powerful feature that allows you to add custom commands to the editor, which can be used to perform various actions, like inserting images, videos, audios, tables, code blocks, etc. You can use in by pressing the / key and then simply start typing the command name.

Note:

You can use ArrowDown, crtl+j or cmd+j, Tabs to navigate down and ArrowUp, ctrl+k or cmd+k to navigate up. Use enter to select the command.

Try it out in demo


Bubble Menu

The Edra component comes with Notion like bubble menus. There are basically 4 bubble menus in action.

  1. Editor Menu: Provides basic editor actions like EdraToolbar
  2. Link Menu: Provides link related actions like copy, delete, edit, etc.
  3. Table Row Menu: Provides table row related actions like insert, delete, etc.
  4. Table Col Menu: Provides table column related actions like insert, delete, etc.

Note:

You can disable all of them by passing false to the showMenu prop.

Warning:

Disabling the showMenu prop will disable all of the bubble menus. You may need to change the code as per your requirement, if you want a different behavior.

Try it out in demo


Font Size

Edra component provides a way to change the font size of the editor. In headless ui, you’ll find a decrease and increase button in the editor toolbar. In shadcn ui, you’ll find a dropdown menu in the editor toolbar.

In either case, you can change the font size of any text in the editor.

Try it out in demo


Placeholder

Edra component provides a way to add placeholder text to the editor. This is a ghost text and can be seen in an empty line. You can change the behavior of the placeholder as per your liking in $lib/components/edra/editor.ts file in your project.

You can also find the same file here in github.

Try it out in demo


Code Block Extended

Edra component provides a way to extend the code block feature. You can add a language selector and copy the code to clipboard. For code highlighting, we’re using lowlight with one-dark and one-light themes, for both dark and light mode respectively. You can find the file for onedark.css in $lib/components/edra/onedark.css file in your project.

You can also find the same file here in github.

Info:

By default, tiptap does not provide a way to select the language or to copy the code. We have extended this functionality in Edra component to better UX.

Warning:

We use html .dark css selector to apply the dark mode theme to the code block. If you feel like to change the css selector, you need to change it in onedark.css file.

Try it out in demo


Image Extended

Edra component let’s you add an image in different ways. You can add an image link or select an image from your local machine and add it to the editor. Moreover, you can also add an image from your clipboard. Edra provides an ImagePlaceholder component which let’s you put a placeholder for future references.

Edra lets you paste an image of upto 2MB in size. You can change this behavior by changing the maxSize value in getHandlePaste function in $lib/components/edra/editor.ts file in your project. You can also find the same file here in github.

You can specify the image maxSize in this manner:

editor.setOptions({
	editorProps: {
		// maximum 3 MBs
		handlePaste: getHandlePaste(editor, 3)
	}
});

Info:

What if I want to process the pasted image? You may want to do some additional processing when user pastes an image. For that you can modify the getHandlePaste function in $lib/components/edra/utils.ts file in your project. You can also find the same file here in github.

Try it out in demo


Video Extended

Similar to ImageExtended, you can add a video in different ways. You can add a video link or select a video from your local machine and add it to the editor. Edra provides a VideoPlaceholder component which let’s you put a placeholder for future references.

Warning:

You can not paste a video in editor from clipboard. You need to upload the video from your local machine, or provide a link to the video.

Try it out in demo


Audio Extended

Similar to ImageExtended and VideoExtended, you can add an audio in different ways. You can add an audio link or select an audio from your local machine and add it to the editor. Edra provides an AudioPlaceholder component which let’s you put a placeholder for future references.

Warning:

You can not paste an audio in editor from clipboard. You need to upload the audio from your local machine, or provide a link to the audio.

Try it out in demo


IFrame Extended

Similar to ImageExtended and VideoExtended, you can add an iframe with a source url.

Edra provides an IFramePlaceholder component which let’s you put a placeholder for future references.

Warning:

You can only add the iframe with a source url. You can not paste an iframe in editor from clipboard.

Try it out in demo


Table

Edra component provides a way to add a table in editor. You can add it by either clicking the table button in EdraToolbar or by using SlashCommand or /table. By Default, a table with 3 columns and 3 rows will be added. Furthermore, you can insert after, before or delete a column or row.

Error:

If you click the table button in EdraToolbar or use it from SlashCommand, while being focused on the table in the editor, the current table will be deleted.

Try it out in demo


Search And Replace

Edra component provides a way to search and replace text in the editor. You can use it by clicking the search button in the EdraToolbar. You can search a text, match case, go to next match and go to previous match.

You can also replace the current text or replace all the text in the editor by using the replace button in the EdraToolbar.

Try it out in demo


Links

Edra component provides an advanced link feature. By default, the links will not open on click. The link have a bubble menu which let’s you edit, open, copy link and delete link.

If you want to change this behavior, you can change the openOnClick value in Link.configure function in $lib/components/edra/editor.ts file in your project. You can also find the same file here in github.

Link.configure({
	openOnClick: true,
	openOnClick: false,
	autolink: true,
	defaultProtocol: 'https',
	HTMLAttributes: {
		target: '_blank',
		rel: 'noopener noreferrer'
	}
})

Try it out in demo


Drag Handle

Edra component provides a way to drag and drop handle in the editor. You can drag a node and drop it in anywhere in the editor. You can find the drag handle component in $lib/components/edra/drag-handle.svelte file in your project and you can also find the same file here in github.

Try it out in demo


LaTeX Support

Edra component provides a way to add LaTeX expressions in the editor. You can add them by using $ symbol. For example, you can write $\sum_{i=1}^n i^2$ to get an equation. You also click on a LaTeX expression and it will be evaluated and the result will be shown.

Try it out in demo

Commands

Below is a list of all available commands that can be passed to the <Edra /> and <EdraToolbar />. A full list of the commands can also be seen in src\lib\edra\commands\commands.ts.

Regular Commands

CommandDescription
undoReverts the last action.
redoRe-applies the previously undone action.
heading1Toggles heading formatting at level 1.
heading2Toggles heading formatting at level 2.
heading3Toggles heading formatting at level 3.
linkPrompts for a URL and creates a hyperlink for the selected text.
boldToggles bold formatting for the selected text.
italicToggles italic formatting for the selected text.
underlineToggles underline formatting for the selected text.
strikeToggles strikethrough formatting for the selected text.
blockquoteToggles blockquote formatting.
superscriptToggles superscript formatting for the selected text.
subscriptToggles subscript formatting for the selected text.
codeToggles inline code formatting for the selected text.
codeBlockToggles code block formatting.
alignLeftAligns the text to the left.
alignCenterCenters the text.
alignRightAligns the text to the right.
alignJustifyJustifies the text alignment.
bulletListToggles bullet list formatting.
orderedListToggles ordered list formatting.
taskListToggles task list (checklist) formatting.
audio-placeholderInserts an audio placeholder.
image-placeholderInserts an image placeholder.
video-placeholderInserts a video placeholder.
iframe-placeholderInserts an iframe placeholder.
colorResets or unsets the text color.
highlightToggles highlighting for the selected text.
tableInserts a 3x3 table if no table is active; deletes the table if one is already active.
font incrementIncreases the font size of the selected text.
font decrementDecreases the font size of the selected text.

Special Commands

CommandDescription
fontSizeButtons to increase and decrease font size and shows the current font size
quickColorColor menu to change the text color and highlight the text
searchAndReplaceText search and replace tool