Skip to content

@vizel/svelte

Svelte 5 components and runes for Vizel editor.

Looking for a guide?

See the Svelte Guide for step-by-step tutorials and common patterns.

Installation

bash
npm install @vizel/svelte

Components

Vizel

All-in-one editor component with built-in bubble menu. This is the recommended way to get started.

svelte
<script lang="ts">
  import { Vizel } from '@vizel/svelte';
  import '@vizel/core/styles.css';
</script>

<Vizel
  initialContent={{ type: 'doc', content: [] }}
  placeholder="Start writing..."
  editable={true}
  autofocus="end"
  showBubbleMenu={true}
  enableEmbed={true}
  class="my-editor"
  features={{ markdown: true }}
  onUpdate={({ editor }) => {}}
  onCreate={({ editor }) => {}}
  onFocus={({ editor }) => {}}
  onBlur={({ editor }) => {}}
/>

Props:

PropTypeDefaultDescription
initialContentJSONContent-Initial editor content (JSON)
initialMarkdownstring-Initial editor content (Markdown)
bind:markdownstring-Two-way Markdown binding
placeholderstring-Placeholder text
editablebooleantrueEditable state
autofocusboolean | 'start' | 'end' | 'all' | number-Auto focus behavior
featuresVizelFeatureOptions-Feature configuration
classstring-CSS class name
showToolbarbooleanfalseShow fixed toolbar above editor
showBubbleMenubooleantrueShow bubble menu on selection
enableEmbedboolean-Enable embed in link editor
flavorVizelMarkdownFlavor"gfm"Markdown output flavor
localeVizelLocale-Locale for translated UI strings
extensionsExtensions-Additional Tiptap extensions
transformDiagramsOnImportbooleantrueTransform diagram code blocks when importing markdown
onUpdate({ editor }) => void-Update callback
onCreate({ editor }) => void-Create callback
onDestroy() => void-Destroy callback
onSelectionUpdate({ editor }) => void-Selection change callback
onFocus({ editor }) => void-Focus callback
onBlur({ editor }) => void-Blur callback
onError(error: VizelError) => void-Error callback

Runes

createVizelEditor

This rune creates and manages a Vizel editor instance.

typescript
import { createVizelEditor } from '@vizel/svelte';

const editor = createVizelEditor(options?: VizelEditorOptions);
// Access: editor.current

Returns: { current: Editor | null }

createVizelState

This rune forces a re-render on editor state changes.

typescript
import { createVizelState } from '@vizel/svelte';

const state = createVizelState(getEditor: () => Editor | null);
// Access: state.current (update count)

Returns: { readonly current: number }

createVizelAutoSave

This rune auto-saves editor content with debouncing.

typescript
import { createVizelAutoSave } from '@vizel/svelte';

const autoSave = createVizelAutoSave(
  getEditor: () => Editor | null,
  options?: VizelAutoSaveOptions
);

Returns:

PropertyTypeDescription
statusVizelSaveStatusCurrent save status (reactive)
hasUnsavedChangesbooleanWhether unsaved changes exist (reactive)
lastSavedDate | nullLast save timestamp (reactive)
errorError | nullLast error (reactive)
save() => Promise<void>Save content manually
restore() => Promise<JSONContent | null>Restore content manually

createVizelMarkdown

This rune provides two-way Markdown synchronization with debouncing.

typescript
import { createVizelMarkdown } from '@vizel/svelte';

const md = createVizelMarkdown(
  getEditor: () => Editor | null,
  options?: VizelMarkdownSyncOptions
);

Options:

OptionTypeDefaultDescription
debounceMsnumber300Debounce delay in milliseconds
transformDiagramsbooleantrueTransform diagram code blocks when setting markdown

Returns:

PropertyTypeDescription
markdownstringCurrent Markdown content (reactive)
setMarkdown(md: string) => voidUpdate editor from Markdown
isPendingbooleanWhether sync is pending (reactive)

getVizelTheme

This rune accesses theme state within VizelThemeProvider context.

typescript
import { getVizelTheme } from '@vizel/svelte';

const theme = getVizelTheme();
// Access: theme.theme, theme.resolvedTheme, theme.setTheme()

Returns:

PropertyTypeDescription
themeVizelThemeCurrent theme setting (reactive)
resolvedThemeVizelResolvedThemeResolved theme (reactive)
systemThemeVizelResolvedThemeSystem preference (reactive)
setTheme(theme: VizelTheme) => voidSet theme function

createVizelCollaboration

This rune tracks real-time collaboration state with a Yjs provider.

typescript
import { createVizelCollaboration } from '@vizel/svelte';

const collab = createVizelCollaboration(
  () => provider,
  { user: { name: 'Alice', color: '#ff0000' } }
);

// Access reactive state
// collab.isConnected, collab.peerCount

Parameters:

ParameterTypeDescription
getProvider() => VizelYjsProvider | null | undefinedGetter function for the Yjs provider
optionsVizelCollaborationOptionsCollaboration options including user info

Returns: CreateVizelCollaborationResult

PropertyTypeDescription
isConnectedbooleanWhether the editor is connected to the server (reactive)
isSyncedbooleanWhether initial sync is complete (reactive)
peerCountnumberNumber of connected peers (reactive)
errorError | nullLast error (reactive)
connect() => voidConnect to the server
disconnect() => voidDisconnect from the server
updateUser(user: VizelCollaborationUser) => voidUpdate user cursor info

createVizelComment

This rune manages document comments and annotations.

typescript
import { createVizelComment } from '@vizel/svelte';

const comment = createVizelComment(
  () => editor.current,
  { key: 'my-comments' }
);

// Access reactive state
// comment.comments, comment.activeCommentId

Parameters:

ParameterTypeDescription
getEditor() => Editor | null | undefinedGetter function for the editor
optionsVizelCommentOptionsComment configuration options

Returns: CreateVizelCommentResult

PropertyTypeDescription
commentsVizelComment[]All stored comments (newest first, reactive)
activeCommentIdstring | nullCurrently active comment ID (reactive)
isLoadingbooleanWhether comments are loading (reactive)
errorError | nullLast error (reactive)
addComment(text: string, author?: string) => Promise<VizelComment | null>Add a comment to the selection
removeComment(commentId: string) => Promise<void>Remove a comment
resolveComment(commentId: string) => Promise<boolean>Resolve a comment
reopenComment(commentId: string) => Promise<boolean>Reopen a comment
replyToComment(commentId: string, text: string, author?: string) => Promise<VizelCommentReply | null>Reply to a comment
setActiveComment(commentId: string | null) => voidSet the active comment
loadComments() => Promise<VizelComment[]>Load comments from storage
getCommentById(commentId: string) => VizelComment | undefinedGet a comment by ID

createVizelVersionHistory

This rune manages document version history with save, restore, and delete operations.

typescript
import { createVizelVersionHistory } from '@vizel/svelte';

const history = createVizelVersionHistory(
  () => editor.current,
  { maxVersions: 20 }
);

// Access reactive state
// history.snapshots, history.isLoading

Parameters:

ParameterTypeDescription
getEditor() => Editor | null | undefinedGetter function for the editor
optionsVizelVersionHistoryOptionsVersion history configuration

Returns: CreateVizelVersionHistoryResult

PropertyTypeDescription
snapshotsVizelVersionSnapshot[]All stored snapshots (newest first, reactive)
isLoadingbooleanWhether history is loading (reactive)
errorError | nullLast error (reactive)
saveVersion(description?: string, author?: string) => Promise<VizelVersionSnapshot | null>Save a new version
restoreVersion(versionId: string) => Promise<boolean>Restore a version
loadVersions() => Promise<VizelVersionSnapshot[]>Load versions from storage
deleteVersion(versionId: string) => Promise<void>Delete a version
clearVersions() => Promise<void>Delete all versions

Components

VizelFindReplace

Find & Replace panel component. This component renders when the Find & Replace extension is open.

svelte
<script lang="ts">
  import { VizelFindReplace } from '@vizel/svelte';
</script>

<VizelFindReplace
  editor={editor.current}
  class="my-find-replace"
  onClose={() => console.log('Closed')}
/>

Props:

PropTypeDefaultDescription
editorEditor | null-Editor instance
classstring-CSS class name
localeVizelLocale-Locale for translated UI strings
onClose() => void-Called when the panel closes

VizelEditor

This component renders the editor content area.

svelte
<VizelEditor editor={editor.current} class="my-editor" />

Props:

PropTypeDefaultDescription
editorEditor | null-Editor instance
classstring-CSS class name

VizelBlockMenu

Block context menu that appears when clicking the drag handle.

svelte
<script>
  import { VizelBlockMenu } from '@vizel/svelte';
</script>

<VizelBlockMenu />
PropTypeDefaultDescription
actionsVizelBlockMenuAction[]vizelDefaultBlockMenuActionsCustom menu actions
nodeTypesVizelNodeTypeOption[]vizelDefaultNodeTypesNode types for "Turn into" submenu
classstring-CSS class name
localeVizelLocale-Locale for translated UI strings

VizelBubbleMenu

This component renders a floating formatting bubble menu.

svelte
<VizelBubbleMenu 
  editor={editor.current}
  class="my-bubble-menu"
/>

Props:

PropTypeDefaultDescription
editorEditor | null-Editor instance
classstring-CSS class name
showDefaultMenubooleantrueShow default formatting menu when no children
pluginKeystring-Custom plugin key for the bubble menu
updateDelaynumber-Delay in ms before updating menu position
shouldShow(props) => boolean-Custom function to control menu visibility
enableEmbedboolean-Enable embed option in link editor

Slots:

SlotDescription
defaultCustom bubble menu content

VizelBubbleMenuDefault

This component renders the default bubble menu with all formatting buttons.

svelte
<VizelBubbleMenuDefault 
  editor={editor.current}
  enableEmbed={false}
/>

Props:

PropTypeDefaultDescription
editorEditor | null-Editor instance
enableEmbedbooleanfalseEnable embed in links

VizelBubbleMenuButton

This component renders an individual bubble menu button.

svelte
<VizelBubbleMenuButton
  icon="lucide:bold"
  isActive={editor.current?.isActive('bold')}
  onclick={() => editor.current?.chain().focus().toggleBold().run()}
/>

VizelBubbleMenuDivider

This component renders a bubble menu divider.

svelte
<VizelBubbleMenuDivider />

VizelToolbar

This component renders a fixed toolbar.

svelte
<script lang="ts">
  import { VizelToolbar } from '@vizel/svelte';
</script>

<VizelToolbar editor={editor.current} class="my-toolbar" />

Props:

PropTypeDefaultDescription
editorEditor | null-Editor instance (falls back to context)
classstring-CSS class name
showDefaultToolbarbooleantrueShow default toolbar content
localeVizelLocale-Locale for translated UI strings
childrenSnippet<[{ editor: Editor }]>-Custom toolbar content

VizelToolbarDefault

This component renders the default toolbar content with grouped formatting buttons.

svelte
<VizelToolbarDefault editor={editor.current} actions={customActions} />

Props:

PropTypeDefaultDescription
editorEditor-Editor instance (required)
classstring-CSS class name
localeVizelLocale-Locale for translated UI strings
actionsVizelToolbarActionItem[]vizelDefaultToolbarActionsCustom actions (supports dropdowns)

VizelToolbarDropdown

This component renders a dropdown toolbar button with a popover of nested actions.

svelte
<VizelToolbarDropdown {editor} dropdown={headingDropdown} />

Props:

PropTypeDefaultDescription
editorEditor-Editor instance (required)
dropdownVizelToolbarDropdownAction-Dropdown action definition (required)
classstring-CSS class name

VizelToolbarOverflow

This component renders a "..." overflow button that shows hidden actions in a popover.

svelte
<VizelToolbarOverflow {editor} actions={overflowActions} />

Props:

PropTypeDefaultDescription
editorEditor-Editor instance (required)
actionsVizelToolbarActionItem[]-Actions to show in overflow (required)
classstring-CSS class name
localeVizelLocale-Locale for translated UI strings

VizelToolbarButton

This component renders an individual toolbar button.

svelte
<VizelToolbarButton
  onclick={() => editor.current?.chain().focus().toggleBold().run()}
  isActive={editor.current?.isActive("bold") ?? false}
  title="Bold (Cmd+B)"
>
  <VizelIcon name="bold" />
</VizelToolbarButton>

Props:

PropTypeDefaultDescription
onclick() => void-Click handler
isActivebooleanfalseActive state
disabledbooleanfalseDisabled state
childrenSnippet-Button content
titlestring-Tooltip text
classstring-CSS class name
actionstring-Action identifier

VizelToolbarDivider

This component renders a divider between toolbar button groups.

svelte
<VizelToolbarDivider />

VizelThemeProvider

This component provides theme context to its children.

svelte
<VizelThemeProvider
  defaultTheme="system"
  storageKey="vizel-theme"
  disableTransitionOnChange={false}
>
  {@render children()}
</VizelThemeProvider>

Props:

PropTypeDefaultDescription
defaultThemeVizelTheme"system"Default theme
storageKeystring"vizel-theme"Storage key
targetSelectorstring-Theme target
disableTransitionOnChangebooleanfalseDisable transitions

VizelSaveIndicator

This component displays the save status.

svelte
<VizelSaveIndicator status={autoSave.status} lastSaved={autoSave.lastSaved} />

Props:

PropTypeDescription
statusVizelSaveStatusSave status
lastSavedDate | nullLast save time
classstringCSS class name

VizelPortal

This component renders its children in a portal.

svelte
<VizelPortal container={document.body}>
  <!-- content -->
</VizelPortal>

Props:

PropTypeDescription
containerHTMLElementPortal target

VizelColorPicker

This component renders a color selection interface.

svelte
<VizelColorPicker
  colors={colors}
  value={currentColor}
  recentColors={recentColors}
  onchange={setColor}
/>

VizelIconProvider

This component provides custom icons for Vizel components.

svelte
<script lang="ts">
import { VizelIconProvider } from '@vizel/svelte';
import type { CustomIconMap } from '@vizel/core';

const icons: CustomIconMap = {
  bold: 'mdi:format-bold',
  italic: 'mdi:format-italic',
};
</script>

<VizelIconProvider {icons}>
  <Vizel />
</VizelIconProvider>

Props:

PropTypeDescription
iconsCustomIconMapMap of icon names to Iconify icon IDs
childrenSnippetChildren

VizelSlashMenu

This component renders the slash command menu.

svelte
<VizelSlashMenu
  items={items}
  command={handleCommand}
  class="my-menu"
/>

Utilities

createVizelSlashMenuRenderer

This function creates a slash menu renderer for the SlashCommand extension.

typescript
import { createVizelSlashMenuRenderer } from '@vizel/svelte';

const suggestion = createVizelSlashMenuRenderer();

const editor = createVizelEditor({
  features: {
    slashCommand: {
      suggestion,
    },
  },
});

Svelte 5 Patterns

Using $derived

svelte
<script lang="ts">
  import { createVizelEditor, createVizelState } from '@vizel/svelte';

  const editor = createVizelEditor();
  createVizelState(() => editor.current);

  const isEmpty = $derived(editor.current?.isEmpty ?? true);
  const characterCount = $derived(
    editor.current?.storage.characterCount?.characters() ?? 0
  );
</script>

<p>{isEmpty ? 'Start typing...' : `${characterCount} characters`}</p>

Using $effect

svelte
<script lang="ts">
  import { createVizelEditor } from '@vizel/svelte';

  const editor = createVizelEditor();

  $effect(() => {
    if (editor.current) {
      console.log('Editor ready');
      
      return () => {
        console.log('Editor destroyed');
      };
    }
  });
</script>

Importing from @vizel/core and @tiptap/core

Framework packages do not re-export from @vizel/core. You must import directly:

typescript
// Framework-specific components and runes
import { 
  Vizel, 
  VizelEditor, 
  VizelBubbleMenu, 
  VizelThemeProvider,
  createVizelEditor,
  createVizelMarkdown,
  createVizelAutoSave,
} from '@vizel/svelte';

// Vizel types and utilities from @vizel/core
import { getVizelEditorState, VIZEL_TEXT_COLORS } from '@vizel/core';
import type { VizelEditorOptions, VizelSaveStatus } from '@vizel/core';

// Tiptap types from @tiptap/core
import { Editor } from '@tiptap/core';
import type { JSONContent } from '@tiptap/core';

Released under the MIT License.