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
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():
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:
Composer::make('message')->autoResize(false);Min and Max Rows
Set the minimum and maximum textarea rows:
Composer::make('message')
->minRows(2) // Start at 2 rows
->maxRows(10); // Scroll after 10 rowsKeyboard Submit
Enter to Submit
By default, pressing Enter submits the form (Shift+Enter adds a newline). Disable this for a standard textarea experience:
Composer::make('message')->submitOnEnter(false);Cmd/Ctrl+Enter to Submit
Also enabled by default. Disable it:
Composer::make('message')->submitOnCmdEnter(false);File Attachments
Enable file attachment support. This adds an attachment button to the composer:
Composer::make('message')->allowAttachments();With attachments disabled, the field submits the message text as a string:
[
'message' => 'Hello',
]With attachments enabled, the same field submits a nested value:
[
'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:
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:
$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:
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:
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:
Composer::make('message')
->allowAttachments()
->maxFileSize(5120); // 5MBVue 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:
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:
Composer::make('message')->showCharacterCount();Max Length
Set a character limit for the textarea:
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:
Composer::make('message')
->allowAttachments()
->maxLength(500)
->showCharacterCount();Submit Button
Custom Label
Change the submit button text:
Composer::make('message')->submitLabel('Send Message');Loading State
Show a spinner on the submit button while the form is processing:
Composer::make('message')->loading();Quick Actions
Add preset buttons that insert predefined text into the composer. Useful for canned responses or template messages:
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
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.
<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>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.