Data inputExtension
Editor
Write formatted text in a form: a textarea becomes a toolbar and an editing area, and keeps the HTML for the form.
| Class name | Type | Description |
|---|---|---|
editorplf:editor | Component | The box the script adds after the textarea |
editor-toolbarplf:editor-toolbar | Part | The toolbar the script adds |
editor-toolplf:editor-tool | Part | A tool button the script adds |
editor-separatorplf:editor-separator | Part | The line between two groups of tools |
editor-contentplf:editor-content | Part | The editing area the script adds |
editor-link-panelplf:editor-link-panel | Part | The link panel the script adds, a Popover's panel |
editor-statusplf:editor-status | Part | The screen-reader status the script adds |
Default
The textarea’s content is the starting HTML. Without JavaScript, the textarea stays and submits what it holds.
<div class="field">
<label class="field-label" for="editor-description">Description</label>
<textarea class="textarea" id="editor-description" name="description" data-plf-editor><p>Lightweight <strong>waterproof</strong> jacket with taped seams.</p><ul><li>Packs into its own pocket</li><li>Recycled nylon shell</li></ul></textarea>
</div><div class="plf:field">
<label class="plf:field-label" for="editor-description">Description</label>
<textarea class="plf:textarea" id="editor-description" name="description" data-plf-editor><p>Lightweight <strong>waterproof</strong> jacket with taped seams.</p><ul><li>Packs into its own pocket</li><li>Recycled nylon shell</li></ul></textarea>
</div>Tools
data-plf-tools lists the tools in order, with | between groups. The editor accepts only their formats, so pasted text keeps nothing else. html shows the HTML in the textarea itself; back in the editor, only those formats stay.
<div class="field">
<label class="field-label" for="editor-bio">Short bio</label>
<textarea class="textarea" id="editor-bio" name="bio" data-plf-editor data-plf-tools="bold italic link"></textarea>
</div><div class="plf:field">
<label class="plf:field-label" for="editor-bio">Short bio</label>
<textarea class="plf:textarea" id="editor-bio" name="bio" data-plf-editor data-plf-tools="bold italic link"></textarea>
</div><div class="field">
<label class="field-label" for="editor-notes">Release notes</label>
<textarea class="textarea" id="editor-notes" name="notes" rows="10" placeholder="What changed in this release?" data-plf-editor></textarea>
</div><div class="plf:field">
<label class="plf:field-label" for="editor-notes">Release notes</label>
<textarea class="plf:textarea" id="editor-notes" name="notes" rows="10" placeholder="What changed in this release?" data-plf-editor></textarea>
</div>Invalid
Set required, aria-invalid and aria-describedby on the textarea: the editor copies them.
Error: write a summary before publishing.
<div class="field">
<label class="field-label" for="editor-summary">Summary</label>
<textarea class="textarea" id="editor-summary" name="summary" required aria-invalid="true" aria-describedby="editor-summary-error" data-plf-editor></textarea>
<p class="field-error" id="editor-summary-error">Error: write a summary before publishing.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="editor-summary">Summary</label>
<textarea class="plf:textarea" id="editor-summary" name="summary" required aria-invalid="true" aria-describedby="editor-summary-error" data-plf-editor></textarea>
<p class="plf:field-error" id="editor-summary-error">Error: write a summary before publishing.</p>
</div><div class="field">
<label class="field-label" for="editor-terms">Terms</label>
<textarea class="textarea" id="editor-terms" readonly data-plf-editor><p>Payment is due within <strong>30 days</strong> of the invoice date.</p></textarea>
</div>
<div class="field">
<label class="field-label" for="editor-archived">Archived note</label>
<textarea class="textarea" id="editor-archived" disabled data-plf-editor><p>This project was archived.</p></textarea>
</div><div class="plf:field">
<label class="plf:field-label" for="editor-terms">Terms</label>
<textarea class="plf:textarea" id="editor-terms" readonly data-plf-editor><p>Payment is due within <strong>30 days</strong> of the invoice date.</p></textarea>
</div>
<div class="plf:field">
<label class="plf:field-label" for="editor-archived">Archived note</label>
<textarea class="plf:textarea" id="editor-archived" disabled data-plf-editor><p>This project was archived.</p></textarea>
</div>JavaScript
Editor needs JavaScript and is an extension: the Full script and Full CSS leave it out. It runs on Tiptap 3, which its Browser script includes. The Full script (pelatform-ui.min.js) and the extension scripts after it start every component on page load, with no code of your own; with Core and single files, the last line starts them. For markup you add later, see Dynamic content.
<link rel="stylesheet" href="pelatform-ui.min.css">
<link rel="stylesheet" href="extensions/editor.min.css">
<script src="pelatform-ui.min.js" defer></script>
<script src="extensions/editor.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.min.css">
<link rel="stylesheet" href="components/button.min.css">
<link rel="stylesheet" href="components/form.min.css">
<link rel="stylesheet" href="components/popover.min.css">
<link rel="stylesheet" href="components/toggle.min.css">
<link rel="stylesheet" href="extensions/editor.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/popover.min.js" defer></script>
<script src="extensions/editor.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script><link rel="stylesheet" href="pelatform-ui.prefixed.min.css">
<link rel="stylesheet" href="extensions/editor.prefixed.min.css">
<script src="pelatform-ui.min.js" defer></script>
<script src="extensions/editor.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.prefixed.min.css">
<link rel="stylesheet" href="components/button.prefixed.min.css">
<link rel="stylesheet" href="components/form.prefixed.min.css">
<link rel="stylesheet" href="components/popover.prefixed.min.css">
<link rel="stylesheet" href="components/toggle.prefixed.min.css">
<link rel="stylesheet" href="extensions/editor.prefixed.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/popover.min.js" defer></script>
<script src="extensions/editor.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script>With ESM imports, install the optional Tiptap peers listed under Extensions, import Editor from @pelatform/ui/extensions/editor and call use(Editor) before init().
Hooks
| Attribute | Purpose |
|---|---|
data-plf-editor |
On a <textarea>: starts the editor; the textarea, hidden, keeps the HTML for its form |
data-plf-tools |
The tools in order, with | between groups: heading-2, heading-3, bold, italic, underline, strike, code, link, bullet-list, ordered-list, blockquote, undo, redo and html, all of them by default |
placeholder |
On the textarea: shown while the editor is empty |
rows |
On the textarea: the minimum height in lines; 6 by default |
required, readonly, disabled, aria-invalid, aria-describedby |
On the textarea: the editor follows them, as it does a disabled <fieldset> |
| Method | Behavior |
|---|---|
PLF. |
Returns the textarea’s instance, creating it for valid markup |
PLF. |
Returns the instance or null |
instance. |
Returns the HTML the textarea holds; an empty string while the editor is empty |
instance. |
Replaces the content without sending events, as setting value does |
instance. |
Moves focus into the editing area |
instance. |
Shows the textarea again with the latest HTML |
Events
Like a textarea, the textarea sends input on each change and change when focus leaves the editor after one, and in the HTML view it sends its own; the editor has no events of its own. Code that sets the textarea’s value and sends input or change updates the editor.
Keyboard
| Key | Action |
|---|---|
| Ctrl+B, Ctrl+I, Ctrl+U | Bold, italic, underline; Cmd on a Mac, as for every shortcut here |
| Ctrl+Shift+S, Ctrl+E | Strikethrough, code |
| Ctrl+Alt+2, Ctrl+Alt+3 | Heading 2, Heading 3 |
| Ctrl+Shift+8, Ctrl+Shift+7 | Bulleted list, numbered list |
| Ctrl+Shift+B | Quote |
| Ctrl+K | Opens the link panel; in it, Enter applies the address and Escape closes it |
| Ctrl+Z, Ctrl+Shift+Z | Undo, redo |
| Tab, Shift+Tab | In a list: indent or outdent the item; elsewhere, leave the editor |
| Alt+F10 | From the text to the toolbar |
| Arrow Left, Arrow Right, Home, End | In the toolbar: move between tools |
| Escape | In the toolbar: back to the text |
Security
The editor writes only its tools’ formats:
| Kind | Tags |
|---|---|
| Blocks | p, h2, h3, ul, ol, li, blockquote |
| Inline formats | strong, em, u, s, code, a with only href |
| Line break | br |
A link’s address is an http(s), mailto or tel address or a path on the site; a typed example.com becomes https://example.com. A request can still carry any HTML, so sanitize it on the server with the same allowlist before you store or show it.
| Variable | Default |
|---|---|
--plf-editor-border |
--plf-input-border, otherwise --plf-color-input |
--plf-editor-radius |
--plf-input-radius, otherwise --plf-radius-lg, 0. (8px) |
--plf-editor-bg, --plf-editor-color |
--plf-input-bg or --plf-color-field, --plf-input-color or --plf-color-text |
--plf-editor-focus-width, --plf-editor-focus-color |
--plf-input-focus-width and --plf-input-focus-color, otherwise --plf-focus-width (2px) and --plf-focus-color |
--plf-editor-invalid-border |
--plf-input-invalid-border, otherwise --plf-color-danger |
--plf-editor-toolbar-border |
--plf-color-border-subtle, the line under the toolbar and between groups |
--plf-editor-max-height |
none: the editor grows with its content; with a height, it scrolls |
The text follows Typography’s --plf-prose-* variables, and the tools Button’s and Toggle’s.