M Majid DS mds
GitHub

mds components

mds:composer

A chat / prompt input with an action bar — an open version of a Flux Pro component.

A textarea that grows with what you type, an action bar around it, and Ctrl/ + Enter to submit the surrounding form. The preview is live — type a few lines into it.

Message
<form wire:submit="send">
    <mds:composer wire:model="prompt" label="Message" label:sr-only placeholder="How can I help you today?">
        <x-slot name="actionsLeading">
            <flux:button size="sm" variant="subtle" square><mds:icon icon="paper-clip" class="size-4" /></flux:button>
            <flux:button size="sm" variant="subtle" square><mds:icon icon="adjustments-horizontal" class="size-4" /></flux:button>
        </x-slot>

        <x-slot name="actionsTrailing">
            <flux:button size="sm" variant="filled" square><mds:icon icon="microphone" class="size-4" /></flux:button>
            <flux:button type="submit" size="sm" variant="primary" square><mds:icon icon="paper-airplane" class="size-4" /></flux:button>
        </x-slot>
    </mds:composer>
</form>
The action bar is plain flux:buttons, so they follow your accent colour. square plus an mds:icon slot keeps the buttons icon-only while drawing Hugeicons rather than heroicons.

With a header

The header slot sits above the input — attachments, a reply-to line, a model picker.

galaxy-phone.jpg
159 KB
<mds:composer placeholder="Write a caption for this image...">
    <x-slot name="header">
        <mds:file-item heading="galaxy-phone.jpg" image="https://picsum.photos/seed/phone/80/80" :size="162400" class="w-full max-w-64">
            <x-slot name="actions">
                <mds:file-item.remove label="Remove image" />
            </x-slot>
        </mds:file-item>
    </x-slot>

    <x-slot name="actionsTrailing">
        <flux:button type="submit" size="sm" variant="primary" square><mds:icon icon="paper-airplane" class="size-4" /></flux:button>
    </x-slot>
</mds:composer>

Inline

One row: the actions sit beside the input instead of under it. Pair it with rows="1".

<mds:composer rows="1" inline placeholder="Write your message...">
    <x-slot name="actionsLeading">
        <flux:button size="sm" variant="ghost" square><mds:icon icon="plus" class="size-4" /></flux:button>
    </x-slot>

    <x-slot name="actionsTrailing">
        <flux:button type="submit" size="sm" variant="primary" square><mds:icon icon="paper-airplane" class="size-4" /></flux:button>
    </x-slot>
</mds:composer>

Input variant

variant="input" trades the pill radius for the same corners as the rest of your form controls, so a composer can sit in a field stack without looking like a visitor.

Name
Message
<div class="w-full max-w-md space-y-4">
    <flux:input label="Name" placeholder="Your name" />

    <mds:composer variant="input" label="Message" placeholder="What's on your mind?">
        <x-slot name="actionsTrailing">
            <flux:button type="submit" size="sm" variant="primary">Send</flux:button>
        </x-slot>
    </mds:composer>
</div>

Height

rows is the height it starts at, max-rows the height it stops growing at — past that the input scrolls.

<mds:composer rows="4" max-rows="8" placeholder="Four rows to start, eight at most..." class="max-w-md" />

Submit behavior

By default Ctrl/ + Enter submits and Enter makes a new line. submit="enter" promotes the bare Enter to sending, and Shift + Enter takes over the new line. Ctrl/ + Enter sends either way.

<form wire:submit="send">
    <mds:composer wire:model="prompt" submit="enter" rows="1" inline placeholder="Enter sends this one...">
        <x-slot name="actionsTrailing">
            <flux:button type="submit" size="sm" variant="primary" square><mds:icon icon="paper-airplane" class="size-4" /></flux:button>
        </x-slot>
    </mds:composer>
</form>
Either way the key press calls form.requestSubmit(), so wire:submit and native validation behave exactly as they would on a click. An open IME candidate window keeps its Enter.

Character counter

Not in Flux: maxlength caps the message and counter shows how much of it is used.

47 / 280
<mds:composer :maxlength="280" counter rows="2" placeholder="280 characters at most..." value="Hi! Every character of this message is counted.">
    <x-slot name="actionsTrailing">
        <flux:button type="submit" size="sm" variant="primary" square><mds:icon icon="paper-airplane" class="size-4" /></flux:button>
    </x-slot>
</mds:composer>
The count is in characters, not bytes — a four-letter Persian word like «سلام» counts as 4, not 8.

A different input

The input slot replaces the textarea. Flux fills it with its Pro editor; anything that collects text works — here a plain flux:textarea keeps its own resize handle.

<mds:composer class="max-w-md">
    <x-slot name="input">
        <flux:textarea rows="3" resize="none" placeholder="Your own control goes here..." class="border-0! bg-transparent! px-2! py-1.5! shadow-none!" />
    </x-slot>

    <x-slot name="actionsTrailing">
        <flux:button type="submit" size="sm" variant="primary" square><mds:icon icon="paper-airplane" class="size-4" /></flux:button>
    </x-slot>
</mds:composer>
Auto-growing, the counter and submit="enter" all belong to the built-in textarea, so a replacement brings its own. Ctrl/ + Enter keeps working either way.

Disabled and invalid

A disabled composer is inert — the whole box, action buttons included, stops taking input. Validation errors come from the error bag for name, or from an explicit error.

Message
<div class="w-full space-y-4">
    <mds:composer disabled rows="1" inline placeholder="Sign in to start writing">
        <x-slot name="actionsTrailing">
            <flux:button type="submit" size="sm" variant="primary" square><mds:icon icon="paper-airplane" class="size-4" /></flux:button>
        </x-slot>
    </mds:composer>

    <mds:composer name="prompt" label="Message" error="A message is required." rows="1" inline>
        <x-slot name="actionsTrailing">
            <flux:button type="submit" size="sm" variant="primary" square><mds:icon icon="paper-airplane" class="size-4" /></flux:button>
        </x-slot>
    </mds:composer>
</div>

Persian output

config('mds.persian_digits') — on by default in a real app, off in these docs — switches the digits and the built-in strings to Persian; :fa="true" does it for a single composer, visible here in the counter («۰ / ۲۸۰»). The transcript above the composer is your own markup — the shape most Livewire chat pages end up with; the date in the reply is mds:jalali-date.

سلام! سفارش من کِی می‌رسد؟
مرسوله شما به دستتان می‌رسد.
پیام
۰ / ۲۸۰
<div class="w-full max-w-lg space-y-4">
    <div class="space-y-3">
        <div class="flex justify-start">
            <div class="max-w-[80%] rounded-2xl rounded-ss-sm bg-zinc-100 px-3 py-2 text-sm dark:bg-white/10">
                سلام! سفارش من کِی می‌رسد؟
            </div>
        </div>

        <div class="flex justify-end">
            <div class="max-w-[80%] rounded-2xl rounded-se-sm bg-accent px-3 py-2 text-sm text-accent-foreground">
                مرسوله شما <mds:jalali-date :date="'2026-08-25'" :fa="true" /> به دستتان می‌رسد.
            </div>
        </div>
    </div>

    <form wire:submit="send">
        <mds:composer wire:model="message" label="پیام" label:sr-only rows="1" max-rows="6" :maxlength="280" counter submit="enter" :fa="true" placeholder="پیام خود را بنویسید...">
            <x-slot name="actionsLeading">
                <flux:button size="sm" variant="ghost" square><mds:icon icon="paper-clip" class="size-4" /></flux:button>
            </x-slot>

            <x-slot name="actionsTrailing">
                <flux:button type="submit" size="sm" variant="primary" square><mds:icon icon="paper-airplane" class="size-4" /></flux:button>
            </x-slot>
        </mds:composer>
    </form>
</div>

Reference

mds:composer

PropDescription
nameField name for a plain form post, and the key the error bag is read with.
valueInitial text. The default slot works too.
placeholderPlaceholder for the input.
labelLabel text. Wraps the composer in a flux:field.
label:sr-onlyKeeps the label for screen readers only. label-sr-only also works.
descriptionHelp text under the composer.
description:sr-onlySame, for screen readers only.
rowsHeight the input starts at, in lines. Default: 2.
max-rowsHeight it stops growing at. Unbounded when absent.
maxlengthCharacter cap on the input.
counterShows the character count. Default: false.
inlinePuts the actions beside the input instead of under it. Default: false.
variantinput matches the corner radius of other form controls.
submitKey that submits: cmd+enter (default) or enter.
autofocusFocuses the input on load.
dirDirection of the input. auto follows what is being typed.
disabledMakes the whole composer inert.
invalidApplies error styling.
errorValidation message. Falls back to the bag for name.
faPersian digits in the counter. Default: config('mds.persian_digits').
wire:modelBinds the input to a Livewire property.
SlotDescription
inputReplaces the built-in textarea.
headerAbove the input — attachments, a reply-to line.
footerUnder the action bar, sharing its row with the counter.
actionsLeadingActions at the start of the action bar.
actionsTrailingActions at the end, usually the submit button.

Related