Markdown Editor
The Markdown Editor turns any <x-textarea> into a lightweight rich-text field. Select some text and a small floating toolbar appears above the selection — clicking a button wraps the selection in the matching markdown markers.
The markdown is also visualized live inside the textarea: bold text renders bold, italic renders italic, and the markers themselves stay visible in a light grey — the same way WhatsApp does it. What you store stays plain markdown, so nothing changes for your backend.
Basic Usage
Add the markdown attribute to any <x-textarea>. That's it — the toolbar and the inline highlighting are enabled for that field only.
1<x-textarea markdown wire:model="description" label="Description" rows="5" />
Limiting the Toolbar
Pass a comma-separated list to show only specific buttons. Every name must exist in the formats map of your config — unknown names are ignored, and if nothing remains the feature is skipped entirely for that field.
1<x-textarea markdown="bold,italic" wire:model="description" label="Description" />
Stylesheet
The feature ships its own stylesheet. If you import the full basic-components stylesheet you already have it. If your project cherry-picks single components, add the import below — otherwise the editor stays disabled and logs a warning to the browser console.
1// If you import the full stylesheet, you already have it:2@import '@basic-components/scss/basic-components';3 4// If you cherry-pick components, add this line:5@import '@basic-components/scss/components/markdown-editor';
Component Attributes
The Markdown Editor is controlled by a single attribute.
| Attribute | Type | Default | Description |
|---|---|---|---|
| markdown | Boolean | — | Enables the toolbar and the inline highlighting with the buttons from markdown.default-buttons. |
| markdown="…" | String | Config default | Comma-separated list of buttons for this field, e.g. markdown="bold,italic". |
| spellcheck | Boolean | false | Spellchecking is turned off on markdown fields, because the browser underlines the markers as typos. Set spellcheck="true" to force it back on. |
Keyboard Shortcuts
The three common formats have shortcuts. Every other format is reachable by selecting text and pressing Tab to step into the toolbar, then Enter or Space.
| Shortcut | Action |
|---|---|
| Cmd/Ctrl + B | Toggle bold |
| Cmd/Ctrl + I | Toggle italic |
| Cmd/Ctrl + U | Toggle underline |
| Cmd/Ctrl + Z | Undo — the native undo stack stays intact, including toolbar actions |
Shortcuts only fire for formats that are actually enabled on the field. Every button toggles: applying a format to an already formatted selection removes the markers again.
Configuration
default-buttons defines which buttons appear when markdown is used without a value. formats defines the markers that get inserted.
1// config/basic-components.php 2 3'markdown' => [ 4 'default-buttons' => ['bold', 'italic', 'underline', 'strikethrough', 'code'], 5 6 'formats' => [ 7 'bold' => ['prefix' => '**', 'suffix' => '**'], 8 'italic' => ['prefix' => '_', 'suffix' => '_'], 9 'underline' => ['prefix' => '__', 'suffix' => '__'],10 'strikethrough' => ['prefix' => '~~', 'suffix' => '~~'],11 'code' => ['prefix' => '`', 'suffix' => '`'],12 ],13],
Custom Markers & Formats
Adjust the markers to whatever your markdown renderer understands. Underline has no official markdown syntax, so the default uses Telegram-style __ — switch it to <u> tags if your renderer passes HTML through.
You can also add your own formats and enable them per field, e.g. markdown="bold,spoiler". Custom formats are highlighted through their dimmed markers and get a pencil icon unless you define one.
1// config/basic-components.php 2 3'markdown' => [ 4 'formats' => [ 5 'bold' => ['prefix' => '**', 'suffix' => '**'], 6 'italic' => ['prefix' => '_', 'suffix' => '_'], 7 8 // Underline as HTML instead of Telegram-style "__" 9 'underline' => ['prefix' => '<u>', 'suffix' => '</u>'],10 11 // Your own format12 'spoiler' => ['prefix' => '||', 'suffix' => '||'],13 ],14],
Toolbar Icons
The buttons render through <x-icon> and default to the bundled Heroicons. If your project uses font icons (icons.type = 'font'), override them with your icon classes.
1// config/basic-components.php 2 3'markdown' => [ 4 'icons' => [ 5 'bold' => 'fa-solid fa-bold', 6 'italic' => 'fa-solid fa-italic', 7 'underline' => 'fa-solid fa-underline', 8 'strikethrough' => 'fa-solid fa-strikethrough', 9 'code' => 'fa-solid fa-code',10 ],11],
Combining with Speech-to-Text
The Markdown Editor and Speech-to-Text work on the same field. Dictated text lands in the textarea as usual and can be formatted right after.
1<x-textarea2 markdown3 speech-to-text4 wire:model="description"5 label="Description"6 rows="6"7/>
Rendering the Output
The field stores plain markdown — this component never renders it for you, so you stay in control of which flavour and which sanitizing rules apply.
1// The component stores plain markdown — rendering it stays your choice. 2// Example with league/commonmark: 3 4use League\CommonMark\CommonMarkConverter; 5 6$converter = new CommonMarkConverter([ 7 'html_input' => 'escape', 8 'allow_unsafe_links' => false, 9]);10 11echo $converter->convert($model->description);
How the Highlighting Works
A <textarea> cannot render styled text on its own. The component therefore draws an exact mirror of the value behind the textarea and makes the textarea's own text transparent — the caret, the selection and every native behaviour stay untouched.
Because the mirror has to line up with the caret character by character, nothing in it may change font metrics: bold is drawn with a text stroke instead of a bolder face, and the italic slant is measured at runtime and dropped automatically if your font substitutes a real italic face with different letter widths. In that case italic is indicated by its grey markers only — a deliberate trade-off in favour of a caret that never drifts.
The mirror is aria-hidden and never receives pointer events, so screen readers and mobile dictation interact with the plain textarea exactly as they would without the feature.
Good to Know
| Topic | Behaviour |
|---|---|
| Graceful degradation | If the stylesheet or the JavaScript never loads, the field stays a perfectly usable plain textarea — the text is never hidden. |
| Livewire | Values updated from the server are picked up automatically. The overlay itself is wire:ignored, so morphs leave it alone. |
| Long passages | A single formatted run is highlighted up to 1000 characters. Longer runs stay plain text — a deliberate cap that keeps typing fast in very large documents. |
| Word boundaries | Markers made of word characters never match inside a word, so snake_case_names are left alone. |
| Paragraphs | A format never spans a blank line, so an unclosed marker cannot format the rest of the document. |
| Printing & high contrast | When printing, or in forced-colors / high-contrast mode, the textarea shows its own text and the mirror steps aside. |