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 format
12 '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-textarea
2 markdown
3 speech-to-text
4 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.