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.
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.
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.
Warning:
The shadcn ui flavor have some prerequisites, so please read the following instructions carefully.
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.
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.
<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 executedchildren?
Pass children components to the editor.<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.
<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.
Below are example use cases for Shadcn and Headless:
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>
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>
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>
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/orEdraBubbleMenu
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>
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);
}
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);
}
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
orcmd+j
,Tabs
to navigate down andArrowUp
,ctrl+k
orcmd+k
to navigate up. Useenter
to select the command.
Try it out in demo
The Edra
component comes with Notion
like bubble menus. There are basically 4 bubble menus in action.
EdraToolbar
Note:
You can disable all of them by passing
false
to theshowMenu
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
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
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
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 thedark
mode theme to the code block. If you feel like to change the css selector, you need to change it inonedark.css
file.
Try it out in demo
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
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
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
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
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 inEdraToolbar
or use it fromSlashCommand
, while being focused on thetable
in the editor, the current table will be deleted.
Try it out in demo
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
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
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
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
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
.
Command | Description |
---|---|
undo | Reverts the last action. |
redo | Re-applies the previously undone action. |
heading1 | Toggles heading formatting at level 1. |
heading2 | Toggles heading formatting at level 2. |
heading3 | Toggles heading formatting at level 3. |
link | Prompts for a URL and creates a hyperlink for the selected text. |
bold | Toggles bold formatting for the selected text. |
italic | Toggles italic formatting for the selected text. |
underline | Toggles underline formatting for the selected text. |
strike | Toggles strikethrough formatting for the selected text. |
blockquote | Toggles blockquote formatting. |
superscript | Toggles superscript formatting for the selected text. |
subscript | Toggles subscript formatting for the selected text. |
code | Toggles inline code formatting for the selected text. |
codeBlock | Toggles code block formatting. |
alignLeft | Aligns the text to the left. |
alignCenter | Centers the text. |
alignRight | Aligns the text to the right. |
alignJustify | Justifies the text alignment. |
bulletList | Toggles bullet list formatting. |
orderedList | Toggles ordered list formatting. |
taskList | Toggles task list (checklist) formatting. |
audio-placeholder | Inserts an audio placeholder. |
image-placeholder | Inserts an image placeholder. |
video-placeholder | Inserts a video placeholder. |
iframe-placeholder | Inserts an iframe placeholder. |
color | Resets or unsets the text color. |
highlight | Toggles highlighting for the selected text. |
table | Inserts a 3x3 table if no table is active; deletes the table if one is already active. |
font increment | Increases the font size of the selected text. |
font decrement | Decreases the font size of the selected text. |
Command | Description |
---|---|
fontSize | Buttons to increase and decrease font size and shows the current font size |
quickColor | Color menu to change the text color and highlight the text |
searchAndReplace | Text search and replace tool |