Starter editor
A full Notion-style editor in one component, added to your app with shadcn-svelte, with every plugin, the ready-made menus, comments and a page with a cover, an icon, a title and properties.
<Edytor /> is deliberately small: rich text, no menus, no handles. The starter is the other end: one component with everything on, built with shadcn-svelte components and copied into your app, so the code is yours.
npx shadcn-svelte@latest add https://edytor.dev/r/starter.json
<script lang="ts">
import { StarterEditor } from '$lib/components/edytor/starter/index.js';
</script>
<StarterEditor />
That is a full editor:
- Every useful bundled plugin: rich text, code, tables, columns, images, files, video and audio, embeds, bookmarks, equations, mentions, a table of contents, markdown shortcuts, find, the marquee selection and block moves from the keyboard, with nesting on. Page links join when you give it
pages, comments when you give it acommentsclient. - The ready-made menus: the slash menu, the block handles and block menu, the selection toolbar and its link card, the table’s grips and menus, the code header and its language list, the callout icon picker, the equation panel, the image toolbar, the empty media panel, the find bar, the marquee box, the suggestion bar and the comment cards.
- The page around the blocks: a cover image, a page icon, a large title and typed page properties (a date picker, selects, people…), all stored in the document’s data, so they sync in a room and undo like the blocks.
- Your shadcn theme: the page, the blocks and every menu read your app’s shadcn tokens, light and
.dark(Customizing).
Try it
Hover the cover to change or reposition it, click the icon, edit a property, or press Enter in the title to start writing. The editor is local to this page: a reload resets it.
Install
The starter is a shadcn-svelte registry item: the CLI copies its source into your app, with the shadcn components it uses, and installs edytor.
-
A SvelteKit app with Tailwind v4.
npx sv create my-app --add tailwindcss, ornpx sv add tailwindcssin an existing one. -
shadcn-svelte.
npx shadcn-svelte@latest initwritescomponents.json, your theme’s tokens (:rootand.dark) in your CSS andcnin$lib/utils. -
The starter.
npx shadcn-svelte@latest add https://edytor.dev/r/starter.json
It writes:
| Where | What |
|---|---|
$lib/components/edytor/starter/ |
StarterEditor.svelte, its plugin list (starterPlugins.ts), its types and the page parts (page/) |
$lib/components/edytor/chrome/ |
the ready-made menus (the edytor-chrome item, added with it) and tokens.css, the editor on your tokens |
$lib/components/ui/ |
the shadcn components they use: button, input, badge, calendar, checkbox, command, popover, select, collapsible, kbd, separator, avatar, textarea |
package.json |
edytor and @internationalized/date |
The paths follow your components.json aliases (#lib/components on a SvelteKit 3 app). Only the menus, without the page: npx shadcn-svelte@latest add https://edytor.dev/r/edytor-chrome.json.
A real page
The props <Edytor> takes pass through, so a document comes and goes as usual, and each part is a prop:
<script lang="ts">
import type { JSONDoc } from 'edytor';
import {
StarterEditor,
type StarterProperty
} from '$lib/components/edytor/starter/index.js';
let { value, save }: { value: JSONDoc; save: (value: JSONDoc) => void } = $props();
const properties: StarterProperty[] = [
{
id: 'status',
name: 'Status',
type: 'select',
options: [
{ value: 'todo', label: 'To do', color: 'gray' },
{ value: 'done', label: 'Done', color: 'green' }
]
},
{ id: 'due', name: 'Due', type: 'date' }
];
const upload = async (file: File) => {
const response = await fetch('/api/upload', { method: 'POST', body: file });
return (await response.json()).url as string;
};
</script>
<StarterEditor {value} onChange={save} {properties} {upload} plugins={{ equation: false }} />
uploadstores what a user adds: an image, a file, a video or an audio block, and the cover or the icon they upload. Without it those accept links only.pluginsturns a bundled plugin off (false) or configures it (Props).- The page’s data rides in the document:
value.data.title,value.data.cover… (Page parts).
Edge cases
- The code is yours. The registry copies files, it does not install a package of the starter: edit them, rename
StarterEditor, drop a part. Runningaddagain offers to overwrite your copy (-odoes it without asking); keep your changes in git and merge. <Edytor />does not grow. The bareedytorpackage has no starter in it: an app that never adds the registry item ships none of it.- It renders on the server from
value(orsnapshot) and hydrates with the same markup; the controls that add a cover, an icon or a property appear once the view is live. Dates are formatted in thelocaleprop, the same text on the server and in the browser. - Your own editor instead. The same chrome, as plugins for your
<Edytor>, is Ready-made menus.