This guide explains how to use SquibView in headless mode to build custom UIs while leveraging SquibView’s powerful rendering and copy/paste capabilities.
Headless mode allows you to use SquibView’s core rendering engine without its built-in UI controls. This lets you:
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
<!-- 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
}
// 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);
});
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>
);
}
<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>
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);
});
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);
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);
}
// 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';
}
});
// 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;
}
}
});
// 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);
/* 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;
}
const editor = newSquibView('#editor', {
showControls: false,
baseClass: 'custom-editor'// Your CSS namespace
});
// Apply your themedocument.querySelector('.custom-editor').classList.add('dark-theme');
// Good: Create once, reuseclassCustomEditor {
constructor(container) {
this.editor = newSquibView(container, {
showControls: false
});
}
// Methods use this.editorbold() { /* ... */ }
italic() { /* ... */ }
}
// Stay in sync with editor state
editor.events.on('content:change', updateUI);
editor.events.on('view:change', updateViewButton);
editor.events.on('text:selected', updateFormatButtons);
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
});
<!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>
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.