Skip to main content
Light Dark System

Editor

<zn-editor> | ZnEditor
Since 1.0 experimental

Short summary of the component’s intended use.

The Editor is a powerful rich text editing component built on top of Quill. It provides a customizable toolbar with formatting options, supports file attachments and uploads, includes emoji and date pickers, and offers AI-assisted writing capabilities.

<zn-editor
  name="content"
  value=""
  code-blocks
  dividers
  links
  images
  emojis
  style="height: 400px">
</zn-editor>

Examples

Basic Editor

A basic editor with default formatting options (headings, bold, italic, underline, colors, lists, etc.).

<zn-editor
  name="description"
  style="height: 300px">
</zn-editor>

Editor with Initial Value

Set initial HTML content using the value attribute.

<zn-editor
  name="content"
  value="<h2>Welcome</h2><p>This editor has <strong>initial content</strong> with formatting.</p>"
  style="height: 300px">
</zn-editor>

Interaction Types

The editor supports two interaction types: chat and ticket. In chat mode, pressing Enter submits the form if there is content. In ticket mode (default), Enter creates a new line.

Chat Mode

In chat mode, pressing Enter will submit the form automatically if the editor has content.

Messages:
<form class="chat-form">
  <zn-editor
    name="message"
    interaction-type="chat"
    style="height: 200px">
  </zn-editor>
  <div style="margin-top: 1rem; padding: 1rem; background: var(--zn-color-neutral-100); border-radius: 4px; font-size: 0.875rem;">
    <strong>Messages:</strong>
    <div id="chat-messages"></div>
  </div>
</form>

<script type="module">
  const form = document.querySelector('.chat-form');
  const messagesDiv = document.getElementById('chat-messages');

  await customElements.whenDefined('zn-editor');

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const formData = new FormData(form);
    const message = formData.get('message');
    if (message) {
      const time = new Date().toLocaleTimeString();
      messagesDiv.innerHTML += `<div style="margin-top: 0.5rem;">[${time}] ${message}</div>`;
    }
  });
</script>

Ticket Mode

In ticket mode (default), Enter creates new lines. Forms must be submitted using a submit button.


Submit Ticket
<form class="ticket-form">
  <zn-editor
    name="ticket-content"
    interaction-type="ticket"
    style="height: 250px">
  </zn-editor>
  <br />
  <zn-button type="submit" color="success">Submit Ticket</zn-button>
</form>

<script type="module">
  const form = document.querySelector('.ticket-form');

  await Promise.all([
    customElements.whenDefined('zn-editor'),
    customElements.whenDefined('zn-button')
  ]);

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const formData = new FormData(form);
    alert('Ticket submitted!\n\n' + formData.get('ticket-content'));
  });
</script>

Enabling Features

The editor has many optional features that can be enabled with boolean attributes.

Code Blocks

Enable code block insertion with the code-blocks attribute.

<zn-editor
  name="content"
  code-blocks
  style="height: 300px">
</zn-editor>

Inline Code

Enable inline code formatting with the code attribute.

<zn-editor
  name="content"
  code
  value="<p>Use inline code for commands like <code>npm install</code></p>"
  style="height: 300px">
</zn-editor>

Dividers

Enable horizontal rule insertion with the dividers attribute.

<zn-editor
  name="content"
  dividers
  value="<p>Section 1</p><hr class='ql-hr'><p>Section 2</p>"
  style="height: 300px">
</zn-editor>

Enable link insertion and editing with the links attribute.

<zn-editor
  name="content"
  links
  value="<p>Visit <a href='https://zinc.style'>our website</a> for more info.</p>"
  style="height: 300px">
</zn-editor>

Images

Enable image insertion with the images attribute.

<zn-editor
  name="content"
  images
  style="height: 300px">
</zn-editor>

Videos

Enable video embedding with the videos attribute.

<zn-editor
  name="content"
  videos
  style="height: 300px">
</zn-editor>

Emojis

Enable emoji picker with the emojis attribute. Users can type : to trigger autocomplete or use the emoji picker from the toolbar.

<zn-editor
  name="content"
  emojis
  value="<p>Express yourself! 😀 🎉 ❤️</p>"
  style="height: 300px">
</zn-editor>

Dates

Enable date picker with the dates attribute.

<zn-editor
  name="content"
  dates
  style="height: 300px">
</zn-editor>

All Features Combined

Enable all features together for a fully-featured editor.

<zn-editor
  name="content"
  code-blocks
  code
  dividers
  links
  images
  videos
  dates
  emojis
  style="height: 400px">
</zn-editor>

File Attachments

Enable file attachments with the attachments attribute. You must also provide an attachment-url for uploading files.


Submit
<form class="attachment-form">
  <zn-editor
    name="message"
    attachments
    attachment-url="/api/upload"
    style="height: 300px">
  </zn-editor>
  <input type="hidden" name="attachments" />
  <br />
  <zn-button type="submit" color="success">Submit</zn-button>
</form>

<script type="module">
  const form = document.querySelector('.attachment-form');

  await Promise.all([
    customElements.whenDefined('zn-editor'),
    customElements.whenDefined('zn-button')
  ]);

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    alert('Form submitted! In a real application, attachments would be uploaded to the server.');
  });
</script>

Custom Toolbar Tools

Add custom tools to the editor toolbar using the zn-editor-tool component in the tools slot.

<zn-editor
  name="content"
  style="height: 300px">
  <zn-editor-tool
    slot="tools"
    icon="lightbulb"
    label="Insert Tip"
    handler="dialog"
    uri="/custom/tip"
    key="tip">
  </zn-editor-tool>
</zn-editor>

Context Menu Quick Actions

Add custom quick actions to the context menu using the zn-editor-quick-action component in the context-items slot.

<zn-editor
  name="content"
  value="<p>Select this text to see custom quick actions in the context menu</p>"
  style="height: 300px">
  <zn-editor-quick-action
    slot="context-items"
    icon="translate"
    label="Translate"
    uri="/api/translate"
    key="translate"
    order="1">
  </zn-editor-quick-action>
  <zn-editor-quick-action
    slot="context-items"
    icon="auto_fix_high"
    label="Improve"
    uri="/api/improve"
    key="improve"
    order="2">
  </zn-editor-quick-action>
</zn-editor>

AI-Assisted Editing

Enable AI features with the ai attribute and provide an ai-path for the AI service endpoint.

<zn-editor
  name="content"
  ai
  ai-path="/api/ai"
  style="height: 350px">
</zn-editor>

Custom Actions Slot

Add custom action buttons below the editor using the actions slot.

Save Draft Publish
<zn-editor
  name="content"
  style="height: 300px">
  <div slot="actions" style="display: flex; gap: 0.5rem;">
    <zn-button color="success" type="button" id="save-draft">Save Draft</zn-button>
    <zn-button color="primary" type="submit">Publish</zn-button>
  </div>
</zn-editor>

<script type="module">
  await customElements.whenDefined('zn-button');

  document.getElementById('save-draft').addEventListener('click', () => {
    alert('Draft saved!');
  });
</script>

Programmatic Content Insertion

You can programmatically insert or replace content in the editor using special data attributes on clickable elements.

Insert Mode

Insert text at the current cursor position using editor-mode="insert".

Insert Template
<div style="margin-bottom: 1rem;">
  <zn-button
    editor-id="demo-editor"
    editor-mode="insert"
    editor-content-id="insert-content">
    Insert Template
  </zn-button>
  <input type="hidden" id="insert-content" value="[Inserted template text]">
</div>

<zn-editor
  id="demo-editor"
  name="content"
  value="<p>Click the button to insert text at cursor position.</p>"
  style="height: 250px">
</zn-editor>

Replace Mode

Replace selected text or entire content using editor-mode="replace".

Replace with Template
<div style="margin-bottom: 1rem;">
  <zn-button
    editor-id="replace-editor"
    editor-mode="replace"
    editor-content-id="replace-content">
    Replace with Template
  </zn-button>
  <input type="hidden" id="replace-content" value="This is replacement text">
</div>

<zn-editor
  id="replace-editor"
  name="content"
  value="<p>Select some text and click the button, or click with no selection to replace all.</p>"
  style="height: 250px">
</zn-editor>

Events

Listen to editor events to respond to user interactions.

Events will appear here…
<zn-editor
  id="event-demo"
  name="content"
  interaction-type="chat"
  style="height: 200px">
</zn-editor>

<div id="event-log" style="margin-top: 1rem; padding: 1rem; background: var(--zn-color-neutral-100); border-radius: 4px; font-family: monospace; font-size: 0.875rem; max-height: 200px; overflow-y: auto;">
  Events will appear here...
</div>

<script type="module">
  const editor = document.getElementById('event-demo');
  const eventLog = document.getElementById('event-log');

  await customElements.whenDefined('zn-editor');

  function logEvent(eventName, detail = '') {
    const time = new Date().toLocaleTimeString();
    const message = `[${time}] ${eventName}${detail ? ': ' + JSON.stringify(detail).substring(0, 50) : ''}`;
    eventLog.innerHTML = message + '<br>' + eventLog.innerHTML;
  }

  editor.addEventListener('zn-change', (e) => {
    logEvent('zn-change', 'content updated');
  });

  editor.addEventListener('zn-submit', (e) => {
    logEvent('zn-submit', {value: e.detail.value.substring(0, 30) + '...'});
  });

  editor.addEventListener('zn-element-added', (e) => {
    logEvent('zn-element-added', 'editor initialized');
  });
</script>

Form Validation

The editor integrates with HTML5 form validation.


Submit
<form class="editor-validation-form">
  <zn-editor
    id="validation-editor"
    name="description"
    style="height: 250px">
  </zn-editor>
  <br />
  <zn-button type="submit" color="success">Submit</zn-button>
</form>

<script type="module">
  const form = document.querySelector('.editor-validation-form');
  const editor = document.getElementById('validation-editor');

  await Promise.all([
    customElements.whenDefined('zn-editor'),
    customElements.whenDefined('zn-button')
  ]);

  // Custom validation: require at least 10 characters
  editor.addEventListener('zn-change', () => {
    const text = editor.value.replace(/<[^>]*>/g, '').trim();
    if (text.length < 10 && text.length > 0) {
      editor.setCustomValidity('Content must be at least 10 characters long');
    } else if (text.length === 0) {
      editor.setCustomValidity('Content is required');
    } else {
      editor.setCustomValidity('');
    }
  });

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    if (form.checkValidity()) {
      alert('Form submitted successfully!');
    } else {
      editor.reportValidity();
    }
  });
</script>

Responsive Toolbar

The toolbar automatically adapts to available space, moving tools into an overflow menu on smaller screens.

Resize this container to see the toolbar adapt

<div style="resize: horizontal; overflow: auto; border: 2px dashed var(--zn-color-neutral-300); padding: 1rem; min-width: 300px; max-width: 100%;">
  <p style="font-size: 0.875rem; color: var(--zn-color-neutral-600); margin-bottom: 0.5rem;">Resize this container to see the toolbar adapt</p>
  <zn-editor
    name="content"
    code-blocks
    dividers
    links
    images
    emojis
    dates
    style="height: 300px">
  </zn-editor>
</div>

Time Tracking Integration

The editor can integrate with hidden time tracking inputs in the form for tracking editing time.


Submit
Time tracking is active. Check form data on submit.
<form class="time-tracking-form">
  <input type="hidden" name="startTime" id="startTime" />
  <input type="hidden" name="openTime" id="openTime" />

  <zn-editor
    name="content"
    style="height: 250px">
  </zn-editor>
  <br />
  <zn-button type="submit" color="success">Submit</zn-button>

  <div style="margin-top: 1rem; font-size: 0.875rem; color: var(--zn-color-neutral-600);">
    Time tracking is active. Check form data on submit.
  </div>
</form>

<script type="module">
  const form = document.querySelector('.time-tracking-form');

  await Promise.all([
    customElements.whenDefined('zn-editor'),
    customElements.whenDefined('zn-button')
  ]);

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const formData = new FormData(form);
    const data = Object.fromEntries(formData);
    alert('Form with time tracking:\n\n' + JSON.stringify(data, null, 2));
  });
</script>

Advanced: Accessing Quill Instance

For advanced use cases, you can access the underlying Quill instance after the editor is initialized.

Get Text Length Insert Text Toggle Bold
<zn-editor
  id="quill-access"
  name="content"
  value="<p>This editor demonstrates direct Quill API access.</p>"
  style="height: 250px">
</zn-editor>

<div style="margin-top: 1rem; display: flex; gap: 0.5rem;">
  <zn-button id="get-length">Get Text Length</zn-button>
  <zn-button id="insert-text">Insert Text</zn-button>
  <zn-button id="format-bold">Toggle Bold</zn-button>
</div>

<script type="module">
  const editor = document.getElementById('quill-access');

  await Promise.all([
    customElements.whenDefined('zn-editor'),
    customElements.whenDefined('zn-button')
  ]);

  // Wait for editor to be fully initialized
  editor.addEventListener('zn-element-added', () => {
    const quill = editor.quillElement;

    document.getElementById('get-length').addEventListener('click', () => {
      const length = quill.getLength();
      alert(`Text length: ${length} characters`);
    });

    document.getElementById('insert-text').addEventListener('click', () => {
      const range = quill.getSelection();
      const index = range ? range.index : 0;
      quill.insertText(index, ' [Inserted via API] ');
    });

    document.getElementById('format-bold').addEventListener('click', () => {
      const range = quill.getSelection();
      if (range) {
        const format = quill.getFormat(range);
        quill.format('bold', !format.bold);
      }
    });
  });
</script>

Importing

If you’re using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use any of the following snippets to cherry pick this component.

To import this component from the CDN using a script tag:

<script type="module" src="https://cdn.jsdelivr.net/npm/@kubex/zinc@1.0.86/dist/components/editor/editor.js"></script>

To import this component from the CDN using a JavaScript import:

import 'https://cdn.jsdelivr.net/npm/@kubex/zinc@1.0.86/dist/components/editor/editor.js';

To import this component using a bundler:

import '@kubex/zinc/dist/components/editor/editor.js';

Slots

Name Description
(default) The default slot.
example An example slot.

Learn more about using slots.

Events

Name React Event Description Event Detail
zn-event-name Emitted as an example. -

Learn more about events.

Custom Properties

Name Description Default
--example An example CSS custom property.

Learn more about customizing CSS custom properties.

Parts

Name Description
base The component’s base wrapper.

Learn more about customizing CSS parts.

Dependencies

This component automatically imports the following dependencies.

  • <zn-example>