TallStackUI 4.0 is here! Seven new components and thousand of improvements. See what's new .

Powerful suite of Blade components for TALL Stack apps

Editor

Editor component.

<x-editor />

Before continuing, it is important to know:

  • The component can be used inside or outside Livewire.
  • Inside Livewire, use wire:model to bind the content.
  • In a normal form, use name . The HTML is mirrored into a hidden input.
  • Without a wire:model or name , the component will throw an exception.
  • The default output is raw HTML content, but you can stamp a class on every element. Learn more about it below.

The default output of the editor is raw HTML content. Tailwind's Preflight strips the list markers and the heading sizes, so the same markup reads as plain lines once it is rendered anywhere else. output-classes stamps a class on every element the editor writes, so the content can be styled wherever it lands.

<x-editor output-classes />

After enable output-classes , every element will carries a class of its own:

<ol class="tsui-editor-numeric-list">
<li class="tsui-editor-list-item"></li>
</ol>

So you will be able to customize the output of the editor with your own CSS, for example:

.tsui-editor-numeric-list { @apply my-2 list-decimal pl-6; }
.tsui-editor-heading-1 { @apply text-3xl font-bold; }
.tsui-editor-paragraph { @apply my-2; }
TallStackUI defines none of them. They are hooks, empty until your the application fills them.

An array changes only the tags it lists and leaves the rest alone. A name has to keep the prefix, which is what the sanitizer recognizes on the way back in, and anything else throws.

<x-editor :output-classes="['ol' => 'tsui-editor-steps']" />

A string is the prefix itself:

<x-editor output-classes="blog-" />
<ol class="blog-numeric-list">
<li class="blog-list-item"></li>
</ol>

Without a string, the prefix comes from output_classes_prefix in the configuration file when output_classes is enable , defaulting to tsui-editor- . The option is off by default, under output_classes , and ignored while markdown is on, since Markdown carries no classes.

// ...
 
'editor' => [
Components\Editor\Component::class,
// ...
[
'markdown' => false,
'output_classes' => true,
'output_classes_prefix' => null,
'toolbar' => [/* ... */],
 
// ...
// ...

The default tags and classes are:

Tag Class
p tsui-editor-paragraph
div tsui-editor-block
h1 tsui-editor-heading-1
h2 tsui-editor-heading-2
h3 tsui-editor-heading-3
h4 tsui-editor-heading-4
h5 tsui-editor-heading-5
ul tsui-editor-bullet-list
ol tsui-editor-numeric-list
li tsui-editor-list-item
blockquote tsui-editor-quote
pre tsui-editor-code-block
code tsui-editor-code
hr tsui-editor-rule
a tsui-editor-link
img tsui-editor-image
strong tsui-editor-bold
em tsui-editor-italic
u tsui-editor-underline
s tsui-editor-strike

The stamp is authoritative rather than incremental. On the way in and after every command the classes are wiped and written again from the tag, so a renamed class, a duplicate, and a class the browser carried onto the wrong element all settle on the next pass. Turning it off stops the stamping, it does not rewrite what is stored.

The prefix should be lowercase, dash separated and end with a dash.

An option to display a label and a hint below the editor.

Keep it under a thousand words
<x-editor label="Article" hint="Keep it under a thousand words" />

Twenty buttons across eight groups. Dividers sit between groups. An unknown slug throws.

<x-editor :toolbar="['style', 'bold', 'italic', 'link', 'image']" />
Slug Group Does
style formatting Dropdown: Paragraph, Heading 1 to 3
blockquote formatting Quote
bold inline Bold
italic inline Italic
underline inline Underline
strikethrough inline Strikethrough
ordered-list lists Numbered list
unordered-list lists Bulleted list
indent lists Nests a list item, or indents any other block by 2rem
outdent lists The reverse
align align Dropdown: Left, Center, Right, Justify
code code Inline code
code-block code Code block
clear-format code Strips formatting from the selection
link insert Opens the link dialog
image insert Opens the image dialog
hr insert Inserts a horizontal rule
undo history Undo
redo history Redo
fullscreen view Fills the viewport

An option to set min-height and max-height. Any CSS unit. Defaults to 12rem and 40rem.

<x-editor min-height="20rem" max-height="60vh" />

An option to lock the editor. They cannot be used together.

<x-editor readonly />
 
<x-editor disabled />

An option to display word and line counters in the footer. On by default.

<x-editor :counters="false" />

The editor stays a WYSIWYG. The bound property stores Markdown instead of HTML.

<x-editor markdown />

Tables, task lists and footnotes are not covered, in either direction.

HTML Markdown
h1 to h5 # to #####
p line, then a blank line
strong **text**
em *text*
s ~~text~~
code `text`
pre > code triple backtick fence
ul > li -
ol > li 1.
nested list two-space indent
blockquote >
hr ---
a [text](href)
img ![alt](src)
br two trailing spaces

Applied as you type. Ctrl+Z undoes the formatting and keeps the characters. Nothing changes inside a code block.

Type Get
# , ## , ### Heading 1 to 3
- , * Bulleted list
1. Numbered list
> Quote
--- then Enter Horizontal rule
triple backtick then Enter Code block
**text** Bold
*text* Italic
`text` Inline code
~~text~~ Strikethrough

An option to allow upload of image using normal Livewire way

<x-editor upload-property="picture" upload-method="storeImage" />
use Livewire\WithFileUploads;
 
class PostForm extends Component
{
use WithFileUploads;
 
public string $content = '';
 
public $picture = null;
 
public function storeImage(): string
{
$this->validate(['picture' => ['image', 'max:5120']]);
 
return asset('storage/'.$this->picture->store('posts', 'public'));
}
}
You need to use both attributes in order to make it work. The upload-property is used to upload the image following the Livewire's way, and the upload-method is used to handle the uploaded image and return the URL of the image.
<x-editor x-on:editor:change="words = $event.detail.words"
x-on:editor:link-inserted="console.log($event.detail.href)"
x-on:editor:image-inserted="console.log($event.detail.src)"
x-on:editor:fullscreen-toggled="console.log($event.detail.on)" />
Event Detail Fires
editor:change { id, html, words, lines } After the debounced sync, on a real change
editor:link-inserted { id, href, text } A link was inserted
editor:image-inserted { id, src, alt, source } An image was inserted
editor:fullscreen-toggled { id, on } Fullscreen was toggled
Shortcut Does
Ctrl/Cmd + B, I, U Bold, italic, underline (no U in Markdown mode)
Ctrl/Cmd + Z Undo
Ctrl/Cmd + Shift + Z, Ctrl + Y Redo
Ctrl/Cmd + K Opens the link dialog with the selection filled
Ctrl/Cmd + 0 to 3 Paragraph, Heading 1 to 3
Tab, Shift + Tab in a list Indent, outdent
Enter in a code block Newline instead of a new paragraph
Ctrl/Cmd + Enter in a code block Leaves the block
Escape Closes the dialog, then leaves fullscreen
Arrow keys on the toolbar Moves between buttons

The sanitizer is defense in depth, not the defense.

The sanitizer strips tags, attributes and style properties outside the configured whitelist, over pasted markup and over the value the editor boots with. The stamped output classes are the one exception: they pass whether the option is on or off, bounded by the prefix. href and src are additionally checked by scheme. allowed_styles is applied after allowed_attributes , so widening the tags that may carry a style cannot widen what that style does. SVG is deliberately absent from the default upload mimes.

Sanitize the content on the server before persisting it and before rendering it back.

There are a lot things that can be configured using the configuration file. For example, you can turn markdown or output-classes on for every editor, or set the default toolbar , output_classes_prefix and heights. The default min-height is 12rem and the default max-height is 40rem in the configuration file.

The editor component reuse other components internally, such as modal and dropdown . You can customize the internal components by using the scopes following the guide of the soft customization .

// The two dialogs are <x-modal> instances under a fixed scope
TallStackUi::customize('modal', scope: 'editor.modal.link')
->block('wrapper.fourth', 'rounded-2xl');
 
TallStackUi::customize('modal', scope: 'editor.modal.image')
->block('wrapper.fourth', 'rounded-2xl');
 
// The toolbar dropdowns are <x-dropdown> instances
TallStackUi::customize('dropdown', scope: 'editor.toolbar')
->block('slot.wrapper', 'p-1');
Scope Component Covers
editor.toolbar dropdown The style and the alignment dropdowns of the toolbar
editor.modal.link modal The dialog that inserts a link
editor.modal.image modal The dialog that inserts an image

Code highlighting provided by Torchlight