Forms
TiptapEditor
Full-featured rich text editor built on TipTap / ProseMirror. Configurable toolbar, optional extensions (tables, images, text align, highlight), and HTML or JSON output.
On this page
Full-featured rich text editor built on TipTap / ProseMirror. Configurable toolbar, optional extensions (tables, images, text align, highlight), and HTML or JSON output.
use NyonCode\WireForms\Components\TiptapEditor;
Setup
None. The editor's JavaScript ships pre-bundled inside the package and the
field's Blade view injects it automatically. There is no npm install, no build
step, and no app.js import to add — just use the field and it works out of the box.
The editor is code-split: the core bundle (TipTap core + the always-on
extensions) is served at /wire-forms/tiptap/tiptap-editor.js, and the opt-in
extensions (withTables() / withImages() / withHighlight() / withTextAlign())
ship in a separate addon bundle that is only loaded when a field on the page
enables one of them. Both share one core chunk, so a page without those extensions
downloads less, and enabling tables never ships a second copy of the editor core.
The <script type="module"> tags are injected once per page via Livewire's
@assets directive; they register the Alpine component tiptapEditor that the
view relies on (Alpine ships with Livewire).
Publishing the asset (optional). To have your web server serve the files instead of the package route, publish them with:
php artisan vendor:publish --tag=laravel-assets --forceThis copies the bundles to
public/vendor/wire-forms/— the whole stack's, not just this package's — and the editor emits those paths from then on, cache-buster included. The publish mirrorsdist/verbatim, so the entries keep resolving their shared chunk relative tovendor/wire-forms/tiptap/. See Getting Started → JavaScript Assets.
Contributors. The bundles are generated from
packages/forms/resources/js/tiptap-editor.jsandtiptap-editor-addons.js, and committed (with the shared chunk) topackages/forms/dist/tiptap/. Rebuild them after editing the source with:npm run build:forms-assets
Basic Usage
TiptapEditor::make('content')
Default Content
The editor opens on the field's ->default() — the canonical default every
component has, no editor-specific method. It is markup, not plain text, so a
template arrives pre-formatted:
TiptapEditor::make('minutes') ->default('<h2>Meeting notes</h2><p>Some <strong>text</strong>.</p><ul><li>First point</li></ul>')
How it resolves, in order:
- The form runtime seeds it.
fill()(and a modal action's initial state) writes->default()into the state bag for any key the caller did not provide, so the editor simply opens on a value that is already there. - The editor seeds it when the host did not — a
nullcolumn, a property bound by hand — applying the default whenever the bound value is empty and pushing the parsed document back into Livewire, so saving a form the user never touched stores the template rather than nothing. - A cleared editor is not empty. Emptying the content stores
<p></p>, so re-opening a document the user deliberately cleared does not bring the default back. On an edit form where the column is genuinelynull, add->defaultOnNull()to let the default fill it server-side too.
Under ->outputJson() the default may be a TipTap JSON document string, or the
same HTML — HTML is parsed into a document and stored as JSON either way.
Custom Toolbar
TiptapEditor::make('content') ->toolbarButtons([ 'bold', 'italic', 'underline', '|', 'h2', 'h3', '|', 'bulletList', 'orderedList', '|', 'link', 'undo', 'redo', ])
Use '|' as a visual separator between groups.
Disable Specific Buttons
TiptapEditor::make('content') ->disableToolbarButtons(['codeBlock', 'code'])
No Toolbar
TiptapEditor::make('content') ->disableAllToolbarButtons()
Extensions
Enable optional extensions individually:
TiptapEditor::make('content') ->withTables() // table insertion + editing ->withImages() // image insertion (via URL prompt) ->withTextAlign() // left / center / right alignment buttons ->withHighlight() // text highlight button
When an extension is enabled, its toolbar button is appended automatically.
Output Format
// Default: HTML string stored in the modelTiptapEditor::make('body')->outputHtml() // Store as TipTap JSON document (serialised as a JSON string)TiptapEditor::make('body')->outputJson()
Character Limit
TiptapEditor::make('summary') ->maxLength(2000) // shows a live counter, enforced by CharacterCount extension
Height
TiptapEditor::make('content') ->minHeight(400) // minimum height in pixels (default 240)
Read-Only / Disabled
TiptapEditor::make('content') ->readOnly() ->disabled(fn () => ! $this->canEdit)
Localization
The editor carries no English of its own. Toolbar tooltips, the heading titles
and the browser prompts opened by the link and image buttons all resolve from
wire-forms::fields.editor.*, so the field follows app()->getLocale(). English
(en) and Czech (cs) ship with the package — a Czech app shows Tučné,
Odrážkový seznam, Nadpis 2, and prompts URL odkazu.
The prompt titles are resolved in PHP and handed to the editor's Alpine config, which is why a locale change reaches strings that live inside the JS bundle.
RichEditor and MarkdownEditor title their toolbars from the very same keys, so the three editors read alike in every locale.
Reword a string, or add a locale, by publishing the translations and editing
lang/vendor/wire-forms/{locale}/fields.php:
php artisan vendor:publish --tag=wire-forms::translations
The button glyphs stay H1 / H2 / H3 in every locale — those are symbols,
not words; the tooltip is what gets translated.
Available Toolbar Buttons
| Key | Description |
|---|---|
bold |
Bold |
italic |
Italic |
underline |
Underline |
strike |
Strikethrough |
code |
Inline code |
highlight |
Highlight (requires withHighlight()) |
h1 |
Heading 1 |
h2 |
Heading 2 |
h3 |
Heading 3 |
bulletList |
Unordered list |
orderedList |
Ordered list |
blockquote |
Blockquote |
codeBlock |
Code block |
link |
Hyperlink (opens URL prompt) |
image |
Image (requires withImages()) |
table |
Insert table (requires withTables()) |
alignLeft |
Left align (requires withTextAlign()) |
alignCenter |
Centre align (requires withTextAlign()) |
alignRight |
Right align (requires withTextAlign()) |
undo |
Undo |
redo |
Redo |
| |
Visual separator |
Comparison with RichEditor
| Feature | RichEditor | TiptapEditor |
|---|---|---|
| Engine | document.execCommand (deprecated) |
ProseMirror (stable) |
| Cross-browser | Inconsistent | Consistent |
| Extensions | None | Tables, images, align, highlight, … |
| Output | HTML | HTML or JSON |
| npm dependency | No | Yes |
| Setup effort | Zero | npm install + one import |
Methods
| Method | Type | Description |
|---|---|---|
toolbarButtons(array) |
array | Override the toolbar button list |
disableToolbarButtons(array) |
array | Remove specific buttons |
disableAllToolbarButtons() |
— | Hide the toolbar entirely |
default(string|Closure) |
string | Pre-formatted document the editor opens on when empty |
defaultOnNull() |
— | Let default() also fill an existing null on fill |
outputHtml() |
— | Store content as HTML (default) |
outputJson() |
— | Store content as TipTap JSON string |
withImages(bool) |
bool | Enable image extension + button |
withTables(bool) |
bool | Enable table extension + button |
withTextAlign(bool) |
bool | Enable text-align extension + buttons |
withHighlight(bool) |
bool | Enable highlight extension + button |
minHeight(int) |
int | Minimum editor height in pixels (default 240) |
maxLength(int|null) |
int | Character limit with live counter |
disabled(bool|Closure) |
bool | Disable the editor |
readOnly(bool|Closure) |
bool | Read-only mode |
required() |
— | Mark as required |
placeholder(string|Closure) |
string | Placeholder shown when empty |
live() |
— | Trigger Livewire update on each change |
debounce(int) |
ms | Debounce delay for live() |
See Common Field API for label, hint, tooltip, and other shared methods.