Skip to content

Composer

The Composer field is a chat-style message input with auto-resize, keyboard submit, file attachments, and quick actions. Think Slack or iMessage: type a message and hit Enter to send. It's built for messaging UIs, comment forms, and AI chat interfaces.

Basic Usage

Example

Press Enter to send , Shift+Enter for new line

php
use InertiaUI\Forms\Fields\Composer;

Composer::make('message');

This renders a textarea that auto-resizes as the user types, starting at 1 row and growing up to 6. Pressing Enter submits the form. Shift+Enter adds a newline.

Set the empty textarea text with placeholder():

php
Composer::make('message')->placeholder('Type your message...');

Auto Resize

Auto-resize is enabled by default. The textarea grows as the user types and shrinks when text is removed. Disable it to get a fixed-height textarea:

php
Composer::make('message')->autoResize(false);

Min and Max Rows

Set the minimum and maximum textarea rows:

php
Composer::make('message')
    ->minRows(2)   // Start at 2 rows
    ->maxRows(10); // Scroll after 10 rows

Keyboard Submit

Enter to Submit

By default, pressing Enter submits the form (Shift+Enter adds a newline). Disable this for a standard textarea experience:

php
Composer::make('message')->submitOnEnter(false);

Cmd/Ctrl+Enter to Submit

Also enabled by default. Disable it:

php
Composer::make('message')->submitOnCmdEnter(false);

File Attachments

Enable file attachment support. This adds an attachment button to the composer:

php
Composer::make('message')->allowAttachments();

With attachments disabled, the field submits the message text as a string:

php
[
    'message' => 'Hello',
]

With attachments enabled, the same field submits a nested value:

php
[
    'message' => [
        'text' => 'Hello',
        'attachments' => [
            // Temporary upload tokens by default
        ],
    ],
]

Composer values match the configured attachment mode. Without attachments, the initial value stays a string or null. With attachments enabled, the field always starts from a nested value with text and attachments keys. Scalars are used as the text value, missing keys are filled, invalid text becomes an empty string, and invalid attachments become an empty array. Hidden conditional Composers reset to the same nested empty value.

Composer attachments use the same upload pipeline as the File field. By default, files are uploaded to a temporary endpoint as soon as they are selected or dropped, and the final form request contains encrypted upload tokens. Laravel keeps those token strings in the request input.

Generated form validation covers the nested message.text, message.attachments, and message.attachments.* paths. The #[Validate] attribute runs those rules, but upload hydration is separate. A FormRequest implementing HasUploads is hydrated automatically. A controller that receives a plain Request and a #[Validate] form may call HandleFileUploads::forRequest($request, ['message.attachments']) before reading orderedFormUploads(). See Reading Submitted Uploads.

Use storeWithForm() when you want raw multipart files in the final form request instead of temporary upload tokens:

php
Composer::make('message')
    ->allowAttachments()
    ->storeWithForm();

storeWithForm() submits native files, so final validation uses normal Laravel file rules on message.attachments.*. Generated form validation applies those rules automatically. A manual Request may validate the same paths directly:

php
$request->validate([
    'message.text' => ['nullable', 'string'],
    'message.attachments' => ['array'],
    'message.attachments.*' => [
        'file',
        'mimetypes:image/png,image/jpeg,application/pdf',
        'max:5120',
    ],
]);

Enable attachment reordering when the user should be able to set the submitted file order before sending:

php
Composer::make('message')
    ->allowAttachments()
    ->reorderable();

The attachments array follows the displayed order after drag and keyboard reordering.

Upload Options

Composer attachments use the shared File Uploads pipeline. Use that guide for temporary uploads, storeWithForm(), custom temporary URLs, named upload routes, chunked(), directToStorage(), upload disks, multipart tuning, upload validators, and token validation.

The frontend packages export ComposerValue and ComposerAttachmentValue for this generated form value.

Accepted File Types

Restrict the file types users may select or drop into the composer:

php
Composer::make('message')
    ->allowAttachments()
    ->acceptedFileTypes(['image/png', 'image/jpeg', 'application/pdf']);

The values are passed to the native file input accept attribute and checked by the Vue and React attachment picker. They also become Laravel mimetypes rules on the real files: during the upload request for token modes, or on message.attachments.* during final validation with storeWithForm(). For exact server-side matching, prefer concrete MIME types such as image/png or application/pdf.

Max File Size

Limit selected attachments by size in kilobytes:

php
Composer::make('message')
    ->allowAttachments()
    ->maxFileSize(5120); // 5MB

Vue and React skip files larger than this limit before adding them to the composer. Laravel validates the same max rule during the upload request for token modes, or on each nested file during final validation with storeWithForm().

Combine the same attachment controls when the composer should accept only a small set of file types:

Example

Press Enter to send , Shift+Enter for new line

php
Composer::make('message')
    ->placeholder('Type your message...')
    ->allowAttachments()
    ->reorderable()
    ->acceptedFileTypes(['image/png', 'image/jpeg', 'application/pdf'])
    ->maxFileSize(5120)
    ->storeWithForm()
    ->submitLabel('Send');

Character Count and Limit

Show Character Count

Display a character counter below the input:

php
Composer::make('message')->showCharacterCount();

Max Length

Set a character limit for the textarea:

php
Composer::make('message')
    ->maxLength(500)
    ->showCharacterCount();

For text-only composers, the field submits a string and maxLength() also adds a top-level max validation rule for that string.

With attachments enabled, the field submits message as an array containing text and attachments. maxLength() applies to the nested message.text validation path in generated rules:

php
Composer::make('message')
    ->allowAttachments()
    ->maxLength(500)
    ->showCharacterCount();

Submit Button

Custom Label

Change the submit button text:

php
Composer::make('message')->submitLabel('Send Message');

Loading State

Show a spinner on the submit button while the form is processing:

php
Composer::make('message')->loading();

Quick Actions

Add preset buttons that insert predefined text into the composer. Useful for canned responses or template messages:

Example

Press Enter to send , Shift+Enter for new line

php
Composer::make('reply')->quickActions([
    ['label' => 'Thanks!', 'value' => 'Thank you for your message. We will get back to you soon.'],
    ['label' => 'On it', 'value' => 'We are looking into this and will update you shortly.'],
    ['label' => 'Resolved', 'value' => 'This issue has been resolved. Please let us know if you need anything else.'],
]);

Reply Composer With Attachments

php
Composer::make('message')
    ->label('Reply')
    ->placeholder('Type your message...')
    ->minRows(2)
    ->maxRows(8)
    ->maxLength(2000)
    ->showCharacterCount()
    ->allowAttachments()
    ->reorderable()
    ->acceptedFileTypes(['image/png', 'image/jpeg', 'application/pdf'])
    ->maxFileSize(10240)
    ->submitLabel('Send')
    ->quickActions([
        ['label' => 'Thanks', 'value' => 'Thanks for reaching out!'],
    ])
    ->required();

This example generates nested text and attachment rules. #[Validate] validates those paths automatically. Temporary upload tokens are resolved separately through the upload helpers described in Reading Submitted Uploads.

All shared field methods remain available. See Form Class, Model Binding, Validation, Conditional Visibility, Authorization, and Styling.

Using the Component Directly

Composer is exported from each stack's components subpath. Vue uses v-model; React uses controlled value and onValueChange.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { Composer } from '@inertiaui/form-vue/components'
import type { ComposerAttachment, ComposerAttachmentValue } from '@inertiaui/form-vue'

const message = ref<ComposerAttachmentValue>({
    text: '',
    attachments: [],
})
const error = ref<string | null>(null)
const sending = ref(false)

const quickActions = [
    { label: 'Thanks', value: 'Thanks for reaching out!' },
    { label: 'On it', value: 'We are looking into this now.' },
]

const fieldParts = {
    editor: 'min-h-24',
    item: 'bg-zinc-50',
    submitButton: 'px-4',
}

function send(value: string, attachments: ComposerAttachment[]) {
    console.log({ value, attachments })
}
</script>

<template>
    <Composer
        v-model="message"
        id="support-reply"
        name="reply"
        label="Reply"
        help="Attach screenshots or PDFs when they help."
        placeholder="Type your message..."
        :error="error"
        :invalid="!!error"
        required
        :precognitive="false"
        :disabled="false"
        :loading="sending"
        :auto-resize="true"
        :min-rows="2"
        :max-rows="8"
        :submit-on-enter="true"
        :submit-on-cmd-enter="true"
        allow-attachments
        reorderable
        :accepted-file-types="['image/png', 'image/jpeg', 'application/pdf']"
        :max-file-size="10240"
        store-with-form
        show-character-count
        :max-length="2000"
        submit-label="Send"
        :quick-actions="quickActions"
        badge="Support"
        badge-class="bg-zinc-100 text-zinc-700"
        label-trailing="Required"
        label-trailing-class="text-zinc-500"
        tooltip="Files are submitted with the reply."
        help-position="below"
        layout="stacked"
        control-position="start"
        label-class="font-medium"
        help-class="text-zinc-500"
        error-class="text-red-600"
        wrapper-class="max-w-2xl"
        control-class="bg-white"
        class="shadow-sm"
        :part-classes="fieldParts"
        :label-sr-only="false"
        @submit="send"
    />
</template>
tsx
import { useState } from 'react'
import { Composer } from '@inertiaui/form-react/components'
import type { ComposerAttachment, ComposerAttachmentValue } from '@inertiaui/form-react'

const quickActions = [
    { label: 'Thanks', value: 'Thanks for reaching out!' },
    { label: 'On it', value: 'We are looking into this now.' },
]

const fieldParts = {
    editor: 'min-h-24',
    item: 'bg-zinc-50',
    submitButton: 'px-4',
}

export default function ReplyComposer() {
    const [message, setMessage] = useState<ComposerAttachmentValue>({
        text: '',
        attachments: [],
    })
    const [error] = useState<string | null>(null)
    const [sending] = useState(false)

    function send(value: string, attachments: ComposerAttachment[]) {
        console.log({ value, attachments })
    }

    return (
        <Composer
            value={message}
            onValueChange={setMessage}
            id="support-reply"
            name="reply"
            label="Reply"
            help="Attach screenshots or PDFs when they help."
            placeholder="Type your message..."
            error={error}
            invalid={Boolean(error)}
            required
            precognitive={false}
            disabled={false}
            loading={sending}
            autoResize
            minRows={2}
            maxRows={8}
            submitOnEnter
            submitOnCmdEnter
            allowAttachments
            reorderable
            acceptedFileTypes={['image/png', 'image/jpeg', 'application/pdf']}
            maxFileSize={10240}
            storeWithForm
            showCharacterCount
            maxLength={2000}
            submitLabel="Send"
            quickActions={quickActions}
            badge="Support"
            badgeClass="bg-zinc-100 text-zinc-700"
            labelTrailing="Required"
            labelTrailingClass="text-zinc-500"
            tooltip="Files are submitted with the reply."
            helpPosition="below"
            layout="stacked"
            controlPosition="start"
            labelClass="font-medium"
            helpClass="text-zinc-500"
            errorClass="text-red-600"
            wrapperClass="max-w-2xl"
            controlClass="bg-white"
            class="shadow-sm"
            partClasses={fieldParts}
            labelSrOnly={false}
            onSubmit={send}
        />
    )
}

Attachment upload transports use the same runtime props described in File Uploads.

Composer is a composite editor and does not forward arbitrary native attributes or listeners to the internal textarea. See Native Attributes And Events.