UI reference

MicroText

Rich text editor wrapper around Tiptap for the Saaslivery platform. Provides a toolbar, output syncing to hidden inputs, and a consistent editing experience across apps.

JS: public/js/microtext.js (ES module) CSS: public/js/microtext.css Version: 0.1.0


Quick start

Include the CSS and the JS module, then initialize:

<link rel="stylesheet" href="/public/js/microtext.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200">

<form method="post" action="/api/notes">
  <div id="editor"></div>
  <input type="hidden" name="body" id="body-input">
  <button type="submit">Save</button>
</form>

<script type="module">
  import Microtext from '/public/js/microtext.js';

  Microtext.init({
    element: '#editor',
    content: '<p>Hello world</p>',
    placeholder: 'Start writing...',
    output: '#body-input',
    format: 'html',
    toolbar: true,
  });
</script>

Note

MicroText loads Tiptap extensions from esm.sh at runtime — no build step needed. The <script> tag must have type="module".


Microtext.init(options)

Returns a Tiptap Editor instance (or null if the element is not found).

Options

Option Type Default Description
element string or HTMLElement required CSS selector or DOM node where the editor mounts. Works with Alpine's $el.
content string '' Initial HTML content.
placeholder string 'Start writing...' Placeholder text shown when the editor is empty.
output string or HTMLElement null Hidden input that receives the editor's output on every change.
format string 'html' Output format written to the output element: 'html' or 'json'.
toolbar boolean true Show the formatting toolbar.
borderless boolean false Remove the border from the editor wrapper.
editable boolean true false renders read-only content (no toolbar, no border).
onUpdate function null (html, json) => {} — called on every content change.

Toolbar buttons

The toolbar is organized into groups separated by thin dividers:

Group Buttons
Formatting Bold, Italic, Underline, Strikethrough
Headings Heading 2, Heading 3
Lists Bullet list, Ordered list, Task list
Blocks Blockquote, Code block, Inline code
Insert Horizontal rule, Link

Buttons highlight when the cursor is inside matching content (e.g., Bold highlights when text is bold).

Clicking the link button opens a prompt() dialog. Enter a URL to set a link, clear the field to remove the link.


Output syncing

When output is set, MicroText automatically writes the editor content to the target element's value on every change. This lets you use a standard hidden <input> inside a <form>:

<div id="editor"></div>
<input type="hidden" name="description" id="desc-input">
Microtext.init({
  element: '#editor',
  output: '#desc-input',
  format: 'html',    // or 'json' for Tiptap JSON
});

On form submit, desc-input contains the current HTML (or JSON).


Read-only mode

Render saved content without editing:

Microtext.init({
  element: '#content-display',
  content: savedHtml,
  editable: false,
});

This removes the toolbar and border, producing clean rendered output.


Using with Alpine.js

<div x-data="{ editor: null }" x-init="
  import('/public/js/microtext.js').then(m => {
    editor = m.default.init({
      element: $refs.editor,
      content: '',
      placeholder: 'Write a comment...',
      output: $refs.hidden,
      toolbar: true,
    });
  })
">
  <div x-ref="editor"></div>
  <input type="hidden" x-ref="hidden" name="comment_body">
</div>

Destroying the editor

The returned editor instance has a destroy_microtext() method that cleans up the Tiptap editor and unwraps the DOM:

var editor = Microtext.init({ element: '#editor' });

// Later, when removing the editor:
editor.destroy_microtext();

Tiptap extensions included

MicroText bundles these Tiptap extensions:

Extension What it adds
StarterKit Paragraphs, headings (H2–H4), bold, italic, strike, code, code block, blockquote, bullet list, ordered list, horizontal rule, hard break.
Underline Underline formatting.
Link Clickable links with rel="noopener noreferrer nofollow".
Placeholder Placeholder text when the editor is empty.
TaskList + TaskItem Checkbox task lists (nested supported).

CSS classes

All classes are prefixed with mt-.

Class Element Purpose
.mt-wrap div Outer wrapper with border and rounded corners.
.mt-wrap:focus-within .mt-wrap Highlights border with --brand color on focus.
.mt-readonly .mt-wrap Removes border for read-only mode.
.mt-borderless .mt-wrap Removes border when borderless: true.
.mt-toolbar div Formatting toolbar container.
.mt-toolbar-btn button Individual toolbar button (28x28px). .active when format is active.
.mt-toolbar-sep span Thin vertical separator between button groups.
.mt-editor div Tiptap content area.
.mt-link a Styled link inside editor content.

CSS custom properties

MicroText reads these CSS variables:

Variable Used for
--bs-border-color Wrapper and toolbar borders
--surface Wrapper and toolbar background
--bg Toolbar background
--bg-sunken Code block and inline code background
--brand Focus border, active toolbar button, blockquote accent, links
--brand-subtle Active toolbar button background
--text-primary Editor text, headings, bold
--text-secondary Toolbar button color
--text-muted Placeholder, strikethrough, muted text

Example: task description field

<div class="form-group">
  <label class="form-label">Description</label>
  <div id="task-desc-editor"></div>
  <input type="hidden" name="description" id="task-desc-hidden">
</div>

<script type="module">
  import Microtext from '/public/js/microtext.js';

  Microtext.init({
    element: '#task-desc-editor',
    content: '{{ task.description | e }}',
    placeholder: 'Add a description...',
    output: '#task-desc-hidden',
    format: 'html',
    onUpdate: function(html) {
      // auto-save via HTMX
      htmx.ajax('PATCH', '/api/task/{{ task.id }}', {
        headers: { 'Content-Type': 'application/json' },
        values: JSON.stringify({ description: html })
      });
    }
  });
</script>