SquibView Headless Mode Guide

This guide explains how to use SquibView in headless mode to build custom UIs while leveraging SquibView’s powerful rendering and copy/paste capabilities.

What is Headless Mode?

Headless mode allows you to use SquibView’s core rendering engine without its built-in UI controls. This lets you:

Why Use Headless Mode?

You Want SquibView’s Power

But With Your Own UI

Basic Headless Setup

importSquibViewfrom'squibview';

// Create headless instance - no controls, just renderingconst editor = newSquibView('#editor-container', {
  showControls: false,      // Hide built-in controlstitleShow: false,         // Hide title barinitialView: 'split',     // Can still use any view modebaseClass: 'my-editor'// Custom CSS class for styling
});

// Now you have full API access without any UI
editor.setContent('# Hello World');
const html = editor.getHTMLSource();  // Get rendered HTML

Building Custom Controls

Example: Custom Toolbar

<!-- Your custom UI --><divclass="my-toolbar"><buttononclick="customBold()">Bold</button><buttononclick="customItalic()">Italic</button><buttononclick="customLink()">Insert Link</button><buttononclick="switchView()">Toggle View</button><buttononclick="copyFormatted()">Copy</button><buttononclick="undoChange()">Undo</button></div><!-- Headless SquibView container --><divid="editor"></div>
// Initialize headless SquibViewconst editor = newSquibView('#editor', {
  showControls: false,
  initialContent: '# My Document'
});

// Custom control functionsfunctioncustomBold() {
  const selection = window.getSelection();
  const selected = selection ? selection.toString() : '';
  if (selected && editor.lastSelectionData) {
    editor.replaceSelectedText(`**${selected}**`, editor.lastSelectionData);
  }
}

functioncustomItalic() {
  const selection = window.getSelection();
  const selected = selection ? selection.toString() : '';
  if (selected && editor.lastSelectionData) {
    editor.replaceSelectedText(`*${selected}*`, editor.lastSelectionData);
  }
}

functioncustomLink() {
  const url = prompt('Enter URL:');
  const selection = window.getSelection();
  const text = selection ? selection.toString() : 'link text';
  if (editor.lastSelectionData) {
    editor.replaceSelectedText(`[${text}](${url})`, editor.lastSelectionData);
  }
}

functionswitchView() {
  editor.toggleView(); // Still works without controls!
}

functioncopyFormatted() {
  editor.copyHTML();  // Copy as rich HTML
}

functionundoChange() {
  editor.revisionUndo();  // Undo last change
}

Advanced Custom UI Integration

Material Design Integration

// Use with Material Design componentsimport { MDCRipple } from'@material/ripple';

const editor = newSquibView('#editor', {
  showControls: false,
  baseClass: 'mdc-editor'
});

// Create Material Design toolbarconst toolbar = document.createElement('div');
toolbar.className = 'mdc-toolbar';
toolbar.innerHTML = `
  <button class="mdc-icon-button material-icons">format_bold</button>
  <button class="mdc-icon-button material-icons">format_italic</button>
  <button class="mdc-icon-button material-icons">link</button>
`;

// Attach handlers
toolbar.querySelectorAll('button').forEach(btn => {
  newMDCRipple(btn);
  btn.addEventListener('click', handleToolbarClick);
});

React Component Wrapper

importReact, { useRef, useEffect, useState } from'react';
importSquibViewfrom'squibview';

functionCustomMarkdownEditor({ initialContent, onChange }) {
  const containerRef = useRef(null);
  const editorRef = useRef(null);
  const [view, setView] = useState('split');

  useEffect(() => {
    // Initialize headless editor
    editorRef.current = newSquibView(containerRef.current, {
      showControls: false,
      initialContent,
      initialView: view
    });

    // Listen for changes
    editorRef.current.on('content-change', (data) => {
      onChange?.(data.content);
    });

    return() => editorRef.current.destroy();
  }, []);

  return (
    <divclassName="custom-editor">
      {/* Custom toolbar */}
      <divclassName="toolbar"><buttononClick={() => editorRef.current.undo()}>
          Undo
        </button><buttononClick={() => editorRef.current.redo()}>
          Redo
        </button><buttononClick={() => {
          setView(v => v === 'split' ? 'src' : v === 'src' ? 'html' : 'split');
          editorRef.current.setView(view);
        }}>
          View: {view}
        </button><buttononClick={() => editorRef.current.copyToClipboard()}>
          Copy
        </button></div>

      {/* Headless SquibView */}
      <divref={containerRef} /></div>
  );
}

Vue Integration

<template>
  <div class="vue-markdown-editor">
    <!-- Custom controls -->
    <div class="editor-toolbar">
      <button @click="bold">B</button>
      <button @click="italic">I</button>
      <button @click="insertHeading">H1</button>
      <button @click="toggleView">{{ viewMode }}</button>
      <button @click="copyContent">Copy</button>
    </div>

    <!-- Headless editor -->
    <div ref="editorContainer"></div>
  </div>
</template>

<script>
import SquibView from 'squibview';

export default {
  data() {
    return {
      editor: null,
      viewMode: 'split'
    };
  },

  mounted() {
    // Initialize headless
    this.editor = new SquibView(this.$refs.editorContainer, {
      showControls: false,
      initialContent: this.content
    });
  },

  methods: {
    bold() {
      const selected = this.editor.getSelectedText();
      this.editor.replaceSelectedText(`**${selected}**`);
    },

    italic() {
      const selected = this.editor.getSelectedText();
      this.editor.replaceSelectedText(`*${selected}*`);
    },

    insertHeading() {
      this.editor.setContent('# ' + this.editor.getContent());
    },

    toggleView() {
      this.editor.toggleView();
      this.viewMode = this.editor.currentView;
    },

    copyContent() {
      this.editor.copyToClipboard('formatted');
    }
  }
};
</script>

Leveraging Core Features

Smart Copy/Paste

Even in headless mode, you get SquibView’s intelligent copy/paste:

const editor = newSquibView('#editor', {
  showControls: false,
  preserveImageTags: false// Convert images to data URLs for portability
});

// Custom copy button with format selectionfunctioncustomCopy(format) {
  // Use different methods based on formatswitch(format) {
    case'markdown':
    case'plain':
      editor.copySource();  // Copy source markdownbreak;
    case'html':
    case'formatted':
      editor.copyHTML();    // Copy rendered HTMLbreak;
  }
  showNotification(`Copied as ${format}`);
}

// Handle paste with custom processing
editor.events.on('content:change', (content, contentType) => {
  console.log('Content changed:', content);
});

Revision History

Build custom undo/redo UI:

// Custom revision control UIfunctionupdateRevisionButtons() {
  const currentIndex = editor.revisionGetCurrentIndex();
  const totalRevisions = editor.revisionNumRevsions();
  const canUndo = currentIndex > 0;
  const canRedo = totalRevisions > 0 && currentIndex < totalRevisions - 1;

  document.getElementById('undo-btn').disabled = !canUndo;
  document.getElementById('redo-btn').disabled = !canRedo;
  document.getElementById('revision-count').textContent =
    `Revision ${totalRevisions}`;
}

editor.events.on('revision:undo', updateRevisionButtons);
editor.events.on('revision:redo', updateRevisionButtons);

Selection Handling

Create context menus and formatting tools:

editor.events.on('text:selected', (data) => {
  if (data && data.text) {
    // Show custom context menushowContextMenu({
      text: data.text,
      position: getMousePosition(),
      actions: [
        { label: 'Bold', action: () =>wrapSelection('**', data) },
        { label: 'Italic', action: () =>wrapSelection('*', data) },
        { label: 'Code', action: () =>wrapSelection('`', data) }
      ]
    });
  }
});

functionwrapSelection(wrapper, selectionData) {
  const text = selectionData.text;
  editor.replaceSelectedText(`${wrapper}${text}${wrapper}`, selectionData);
}

Common Patterns

Floating Toolbar

// Show toolbar near selection
editor.events.on('text:selected', (data) => {
  const toolbar = document.getElementById('floating-toolbar');

  if (data && data.text) {
    const selection = window.getSelection();
    if (selection.rangeCount > 0) {
      const range = selection.getRangeAt(0);
      const rect = range.getBoundingClientRect();

      toolbar.style.display = 'block';
      toolbar.style.left = `${rect.left}px`;
      toolbar.style.top = `${rect.top - 40}px`;
    }
  } else {
    toolbar.style.display = 'none';
  }
});

Command Palette

// Keyboard shortcut systemdocument.addEventListener('keydown', (e) => {
  if (e.ctrlKey || e.metaKey) {
    switch(e.key) {
      case'b':
        e.preventDefault();
        makeSelectionBold();
        break;
      case'i':
        e.preventDefault();
        makeSelectionItalic();
        break;
      case'k':
        e.preventDefault();
        insertLink();
        break;
      case's':
        e.preventDefault();
        saveDocument();
        break;
    }
  }
});

Status Bar

// Custom status barfunctionupdateStatusBar() {
  const content = editor.getContent();
  const wordCount = content.split(/\s+/).length;
  const charCount = content.length;

  document.getElementById('status').innerHTML = `
    Words: ${wordCount} |
    Characters: ${charCount} |
    View: ${editor.currentView} |
    Type: ${editor.inputContentType}
  `;
}

editor.events.on('content:change', updateStatusBar);
editor.events.on('view:change', updateStatusBar);

Styling Headless Mode

Remove All Default Styling

/* Hide any remaining controls */.my-editor.squibview-controls {
  display: none !important;
}

/* Custom editor styling */.my-editor.squibview-input {
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  font-family: 'JetBrains Mono', monospace;
}

.my-editor.squibview-output {
  background: #f8f9fa;
  padding: 20px;
}

Match Your Design System

const editor = newSquibView('#editor', {
  showControls: false,
  baseClass: 'custom-editor'// Your CSS namespace
});

// Apply your themedocument.querySelector('.custom-editor').classList.add('dark-theme');

Best Practices

1. Keep the Editor Instance

// Good: Create once, reuseclassCustomEditor {
  constructor(container) {
    this.editor = newSquibView(container, {
      showControls: false
    });
  }

  // Methods use this.editorbold() { /* ... */ }
  italic() { /* ... */ }
}

2. Listen to Events

// Stay in sync with editor state
editor.events.on('content:change', updateUI);
editor.events.on('view:change', updateViewButton);
editor.events.on('text:selected', updateFormatButtons);

3. Preserve Features

Don’t disable features unnecessarily:

// Good: Keep all features, just hide UIconst editor = newSquibView('#editor', {
  showControls: false,
  // Keep these enabled:autoload_deps: { all: true },  // Keep diagram, math supportpreserveImageTags: false// Keep smart image handling
});

Example: Complete Custom UI

<!DOCTYPE html><html><head><linkrel="stylesheet"href="squibview.min.css"><style>.editor-wrapper {
      display: flex;
      flex-direction: column;
      height: 100vh;
    }

    .custom-toolbar {
      display: flex;
      gap: 8px;
      padding: 12px;
      background: #2c3e50;
    }

    .toolbar-btn {
      padding: 8px16px;
      background: #34495e;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }

    .toolbar-btn:hover {
      background: #4a5f7f;
    }

    .toolbar-btn.active {
      background: #3498db;
    }

    #editor {
      flex: 1;
      overflow: auto;
    }
  </style></head><body><divclass="editor-wrapper"><!-- Custom Toolbar --><divclass="custom-toolbar"><buttonclass="toolbar-btn"onclick="app.format('bold')">Bold</button><buttonclass="toolbar-btn"onclick="app.format('italic')">Italic</button><buttonclass="toolbar-btn"onclick="app.format('code')">Code</button><divstyle="flex: 1"></div><buttonclass="toolbar-btn"onclick="app.changeView()">View</button><buttonclass="toolbar-btn"onclick="app.copy()">Copy</button><buttonclass="toolbar-btn"onclick="app.undo()">Undo</button><buttonclass="toolbar-btn"onclick="app.redo()">Redo</button></div><!-- Headless Editor --><divid="editor"></div></div><scripttype="module">importSquibViewfrom'./squibview.esm.min.js';

    classCustomMarkdownApp {
      constructor() {
        this.editor = newSquibView('#editor', {
          showControls: false,
          initialContent: '# Welcome\n\nStart writing...',
          initialView: 'split'
        });

        this.setupEventListeners();
      }

      format(type) {
        const selected = this.editor.getSelectedText();
        if (!selected) return;

        const formats = {
          bold: `**${selected}**`,
          italic: `*${selected}*`,
          code: `\`${selected}\``
        };

        this.editor.replaceSelectedText(formats[type]);
      }

      changeView() {
        this.editor.toggleView();
        this.updateViewButton();
      }

      copy() {
        this.editor.copyToClipboard('formatted');
        this.showNotification('Copied!');
      }

      undo() {
        this.editor.undo();
      }

      redo() {
        this.editor.redo();
      }

      setupEventListeners() {
        this.editor.on('content-change', () => {
          console.log('Content updated');
        });

        this.editor.on('view-change', (data) => {
          console.log('View changed to:', data.newView);
        });
      }

      showNotification(message) {
        // Your notification systemconsole.log(message);
      }

      updateViewButton() {
        // Update button state based on current view
      }
    }

    // Initialize appwindow.app = newCustomMarkdownApp();
  </script></body></html>

Summary

Headless mode lets you use SquibView as a powerful rendering engine while building your own UI. You get:

Without:

This makes SquibView perfect for integrating into existing applications, building custom editors, or creating branded experiences while keeping all the powerful rendering and editing capabilities.

Related Documentation