Form
Accessible, composable Blade components for building forms. All components live under the x-pajak-form namespace. Labels, hints, and validation messages are built into each component via props.
Form
A <form> wrapper that handles CSRF injection, method spoofing (PUT, PATCH, DELETE), and renders a translatable submit button automatically. Supports three layout modes via the :layout prop: Stacked (default), Inline (label beside field), and Sectioned (two-column sections with x-pajak-form::section).
<x-pajak-form::form action="#"> <x-pajak-form::email name="demo-email" label="Email" placeholder="[email protected]" /> <x-pajak-form::password name="demo-password" placeholder="Password" /> </x-pajak-form::form>
@use('Pajak\Ui\Common\Enums\Size')
<x-pajak-form::form action="#" submit-text="Subscribe" :submit-size="Size::Lg">
<x-pajak-form::email name="demo-sub-email" label="Email" placeholder="[email protected]" />
</x-pajak-form::form>
@use('Pajak\Ui\Common\Enums\Method')
<x-pajak-form::form action="#" :method="Method::Put" submit-text="Save changes">
<x-pajak-form::input name="fm-name" label="Name" value="John Doe" />
</x-pajak-form::form>
@use('Pajak\Ui\Common\Enums\Method')
@use('Pajak\Ui\Common\Enums\Size')
<x-pajak-form::form
action="#"
:method="Method::Delete"
submit-text="Delete account"
:submit-size="Size::Sm"
>
</x-pajak-form::form>
@use('Pajak\Ui\Form\Enums\FormLayout')
<x-pajak-form::form action="#" :layout="FormLayout::Inline">
<x-pajak-form::input name="description" label="Description" placeholder="Item description" />
<x-pajak-form::select name="category" label="Category" :options="['travel' => 'Travel', 'office' => 'Office', 'software' => 'Software']" />
<x-pajak-form::input name="amount" label="Amount" placeholder="0.00" />
</x-pajak-form::form>
@use('Pajak\Ui\Form\Enums\FormLayout')
<x-pajak-form::form action="#" :layout="FormLayout::Sectioned">
<x-pajak-form::section title="Account" description="How you sign in and how we reach you.">
<x-pajak-form::input name="display_name" label="Display name" placeholder="John Doe" />
<x-pajak-form::email name="email" label="Email" placeholder="[email protected]" />
</x-pajak-form::section>
<x-pajak-form::section title="Notifications" description="Choose what you want to hear about.">
<x-pajak-form::toggle name="filing_reminders" label="Filing reminders" checked />
<x-pajak-form::toggle name="product_news" label="Product news" />
</x-pajak-form::section>
</x-pajak-form::form>
<x-pajak-form::form action="/playground/async" redirect="/"> <x-pajak-form::email name="email" label="Email" placeholder="[email protected]" /> </x-pajak-form::form>
<x-pajak-form::form action="/playground/async" id="demo-async-form"> <x-pajak-form::email name="email" label="Email" placeholder="[email protected]" /> </x-pajak-form::form> <script type="module"> const form = document.getElementById('demo-async-form'); form.addEventListener('pajak:form:success', (e) => { console.log('Success:', e.detail.response); }); form.addEventListener('pajak:form:error', (e) => { console.log('Error:', e.detail.status, e.detail.response); }); form.addEventListener('pajak:form:before-submit', (e) => { // mutate FormData or cancel with e.preventDefault() e.detail.data.set('source', 'demo'); }); </script>
<x-pajak-form::form action="/playground/async" id="demo-extra-data-form"> <x-pajak-form::email name="email" label="Email" placeholder="[email protected]" /> </x-pajak-form::form> <script type="module"> const { PajakForm } = window.Pajak; const form = document.getElementById('demo-extra-data-form'); PajakForm.addData(form, 'locale', 'sk'); PajakForm.addData(form, 'source', 'sidebar'); </script>
Group
A layout wrapper for form fields. Stacks fields vertically with consistent spacing by default. Add :inline="true" to place fields side by side in equal-width columns. Use it when grouping fields outside of a <x-pajak-form::form> - for example, inside a modal or sidebar panel.
<x-pajak-form::group> <x-pajak-form::input name="first_name" label="First name" placeholder="John" /> <x-pajak-form::input name="last_name" label="Last name" placeholder="Doe" /> <x-pajak-form::email name="email" label="Email" placeholder="[email protected]" /> </x-pajak-form::group>
<x-pajak-form::group :inline="true"> <x-pajak-form::input name="first_name" label="First name" placeholder="John" /> <x-pajak-form::input name="last_name" label="Last name" placeholder="Doe" /> <x-pajak-form::select name="title" label="Title" :options="['mr' => 'Mr.', 'ms' => 'Ms.', 'dr' => 'Dr.']" /> </x-pajak-form::group>
<x-pajak-form::group> <x-pajak-form::input name="company" label="Company" placeholder="Acme Inc." /> <x-pajak-form::select name="role" label="Role" :options="['admin' => 'Admin', 'editor' => 'Editor', 'viewer' => 'Viewer']" /> <x-pajak-form::toggle name="active" label="Active account" /> </x-pajak-form::group>
Section
A two-column section divider for use inside a FormLayout::Sectioned form. The left column holds a title and optional description; the right column is the field body slot. Sections are separated by a horizontal border automatically. Can also be used standalone outside a form.
@use('Pajak\Ui\Form\Enums\FormLayout')
<x-pajak-form::form action="#" :layout="FormLayout::Sectioned">
<x-pajak-form::section title="Account" description="How you sign in and how we reach you.">
<x-pajak-form::input name="display_name" label="Display name" placeholder="John Doe" />
<x-pajak-form::email name="email" label="Email" placeholder="[email protected]" />
</x-pajak-form::section>
<x-pajak-form::section title="Notifications" description="Choose what you want to hear about.">
<x-pajak-form::toggle name="filing_reminders" label="Filing reminders" checked />
<x-pajak-form::toggle name="product_news" label="Product news" />
</x-pajak-form::section>
</x-pajak-form::form>
<x-pajak-form::section title="Preferences" description="Adjust your default settings."> <x-pajak-form::select name="language" label="Language" :options="['en' => 'English', 'sk' => 'Slovak', 'cs' => 'Czech']" /> <x-pajak-form::toggle name="dark_mode" label="Dark mode" /> </x-pajak-form::section>
Input
A single-line text field. Supports all HTML input types, three sizes, three states, and an optional leading icon slot.
@use('Pajak\Ui\Common\Enums\Size')
<x-pajak-form::input name="input-sm" :size="Size::Sm" placeholder="Small (sm)" />
<x-pajak-form::input name="input-md" :size="Size::Md" placeholder="Medium (md) - default" />
<x-pajak-form::input name="input-lg" :size="Size::Lg" placeholder="Large (lg)" />
@use('Pajak\Ui\Common\Enums\State')
<x-pajak-form::input name="input-default" placeholder="Default" />
<x-pajak-form::input name="input-error" :state="State::Error" placeholder="Error state" />
<x-pajak-form::input name="input-success" :state="State::Success" placeholder="Success state" />
<x-pajak-form::input name="input-disabled" placeholder="Disabled" :disabled="true" />
<x-pajak-form::input name="input-icon" placeholder="Search…"> <x-slot:icon> <x-heroicon-o-magnifying-glass width="16" height="16" /> </x-slot:icon> </x-pajak-form::input>
<x-pajak-form::input name="username" value="john_doe" />
<x-pajak-form::input name="email-inline" type="email" value="not-an-email" error="Please enter a valid email address." />
A convenience wrapper around x-pajak-form::input with type="email". Accepts all the same props as Input. Defaults autocomplete to 'email'.
<x-pajak-form::email name="email-default" placeholder="[email protected]" />
@use('Pajak\Ui\Common\Enums\State')
<x-pajak-form::email name="email-filled" value="[email protected]" />
<x-pajak-form::email name="email-err" placeholder="[email protected]" :state="State::Error" />
<x-pajak-form::email name="email-ok" value="[email protected]" :state="State::Success" />
<x-pajak-form::email name="email-dis" value="[email protected]" :disabled="true" />
Password
A password input with a visibility toggle. Pass :confirmation="true" to render an inline confirmation field below it.
<x-pajak-form::password name="pw-default" placeholder="Enter password" />
<x-pajak-form::password name="pw-confirm" placeholder="Enter password" :confirmation="true" confirmation-placeholder="Confirm password" />
<x-pajak-form::password name="pw-err" placeholder="Enter password" error="Password must be at least 8 characters." />
<x-pajak-form::password name="pw-conf-err" placeholder="Enter password" :confirmation="true" confirmation-placeholder="Confirm password" confirmation-error="Passwords do not match." />
@use('Pajak\Ui\Common\Enums\Size')
<x-pajak-form::password name="pw-sm" :size="Size::Sm" placeholder="Small" />
<x-pajak-form::password name="pw-md" placeholder="Medium (default)" />
<x-pajak-form::password name="pw-lg" :size="Size::Lg" placeholder="Large" />
Number
A convenience wrapper around x-pajak-form::input with type="number". Pass min, max, and step directly as HTML attributes.
<x-pajak-form::number name="qty" placeholder="0" min="0" max="100" step="1" />
@use('Pajak\Ui\Common\Enums\State')
<x-pajak-form::number name="num-filled" value="42" min="0" max="100" />
<x-pajak-form::number name="num-err" placeholder="0" :state="State::Error" />
<x-pajak-form::number name="num-ok" value="10" :state="State::Success" />
<x-pajak-form::number name="num-dis" value="5" :disabled="true" />
Tel
A convenience wrapper around x-pajak-form::input with type="tel". Accepts all the same props as Input. Pass a TelPattern enum case for built-in locale patterns and placeholders.
<x-pajak-form::tel name="phone" placeholder="+1 555 000 0000" />
@use('Pajak\Ui\Form\Enums\TelPattern')
<x-pajak-form::tel
name="phone-sk"
:pattern="TelPattern::Sk"
:placeholder="TelPattern::Sk->placeholder()"
/>
<x-pajak-form::tel
name="phone-cz"
:pattern="TelPattern::Cz"
:placeholder="TelPattern::Cz->placeholder()"
/>
<x-pajak-form::tel
name="phone-intl"
:pattern="TelPattern::International"
:placeholder="TelPattern::International->placeholder()"
/>
@use('Pajak\Ui\Common\Enums\State')
<x-pajak-form::tel name="tel-filled" value="+421 912 345 678" />
<x-pajak-form::tel name="tel-err" placeholder="+421 912 345 678" :state="State::Error" />
<x-pajak-form::tel name="tel-ok" value="+421 912 345 678" :state="State::Success" />
<x-pajak-form::tel name="tel-dis" value="+421 912 345 678" :disabled="true" />
Url
A convenience wrapper around x-pajak-form::input with type="url". Accepts all the same props as Input.
<x-pajak-form::url name="website" placeholder="https://example.com" />
@use('Pajak\Ui\Common\Enums\State')
<x-pajak-form::url name="url-filled" value="https://example.com" />
<x-pajak-form::url name="url-err" placeholder="https://example.com" :state="State::Error" />
<x-pajak-form::url name="url-ok" value="https://valid.com" :state="State::Success" />
<x-pajak-form::url name="url-dis" value="https://readonly.com" :disabled="true" />
Textarea
A resizable multi-line text field. Accepts the same state modifiers as Input.
@use('Pajak\Ui\Common\Enums\State')
<x-pajak-form::textarea name="ta-default" placeholder="Write something…" />
<x-pajak-form::textarea name="ta-error" :state="State::Error" placeholder="Error state" />
<x-pajak-form::textarea name="ta-disabled" placeholder="Disabled" :disabled="true" :rows="2" />
@use('Pajak\Ui\Common\Enums\State')
<x-pajak-form::textarea name="ta-success" value="Looks good." :state="State::Success" :rows="3" />
<x-pajak-form::textarea name="ta-prefilled" value="This is a pre-filled message." :rows="3" />
<x-pajak-form::textarea name="ta-err-inline" placeholder="Write something…" error="This field is required." :rows="3" />
Select
A custom-styled select backed by a native <select>. Supports single select, searchable, option groups with metadata, and multiselect with dismissible chips. JavaScript upgrades the trigger and dropdown while keeping the native element accessible and form-submittable.
@use('Pajak\Ui\Common\Enums\State')
<x-pajak-form::select name="sel-default" placeholder="Choose a plan…">
<option value="free">Free</option>
<option value="pro">Pro</option>
<option value="enterprise">Enterprise</option>
</x-pajak-form::select>
<x-pajak-form::select name="sel-searchable" placeholder="Choose a country" :searchable="true"> <option value="sk">Slovakia</option> <option value="cz">Czech Republic</option> <option value="de">Germany</option> <option value="at">Austria</option> <option value="pl">Poland</option> <option value="hu">Hungary</option> </x-pajak-form::select>
<x-pajak-form::select name="sel-grouped" placeholder="Select income source" :searchable="true"> <optgroup label="Recent"> <option value="acme" data-meta="PIT-11">Acme s.r.o.</option> <option value="globex" data-meta="PIT-11">Globex s.r.o.</option> </optgroup> <optgroup label="Other sources"> <option value="freelance" data-meta="PIT-36">Freelance / B2B</option> <option value="rental" data-meta="PIT-28">Rental income</option> </optgroup> </x-pajak-form::select>
<x-pajak-form::select name="sel-deductions" placeholder="Add deduction…" :multiple="true" :options="['internet' => 'Internet relief', 'charity' => 'Charity donation', 'rehab' => 'Rehabilitation', 'children' => 'Children relief']" :value="['internet']" />
<x-pajak-form::select name="sel-preselected" value="pro"> <option value="free">Free</option> <option value="pro">Pro</option> <option value="enterprise">Enterprise</option> </x-pajak-form::select>
<x-pajak-form::select name="sel-err" placeholder="Choose a plan…" error="Please select a plan."> <option value="free">Free</option> <option value="pro">Pro</option> <option value="enterprise">Enterprise</option> </x-pajak-form::select>
<x-pajak-form::select name="sel-disabled" placeholder="Disabled select" :disabled="true"> <option value="a">Option A</option> </x-pajak-form::select>
Checkbox
A styled checkbox with an optional label and description. Pass :checked="true" to pre-check it, and :disabled="true" to disable.
<x-pajak-form::checkbox name="cb-label" value="1" label="Remember me" /> <x-pajak-form::checkbox name="cb-checked" value="1" label="Checked by default" :checked="true" /> <x-pajak-form::checkbox name="cb-desc" value="1" label="Subscribe to updates" description="Get notified about new features and releases." /> <x-pajak-form::checkbox name="cb-disabled" value="1" label="Disabled" :disabled="true" />
<x-pajak-form::checkbox name="roles[]" value="admin" label="Admin" description="Full system access." :checked="true" /> <x-pajak-form::checkbox name="roles[]" value="editor" label="Editor" description="Can create and edit content." :checked="true" /> <x-pajak-form::checkbox name="roles[]" value="viewer" label="Viewer" description="Read-only access." /> <x-pajak-form::checkbox name="roles[]" value="billing" label="Billing" description="Manage invoices and subscriptions." :disabled="true" />
<x-pajak-form::checkbox name="terms" value="1" label="I accept the terms and conditions" error="You must accept the terms to continue." />
Radio
Standard radio buttons. Group them by sharing the same name. Supports labels, descriptions, and disabled state.
<x-pajak-form::radio name="plan" value="free" label="Free" description="Up to 3 projects, community support." :checked="true" /> <x-pajak-form::radio name="plan" value="pro" label="Pro" description="Unlimited projects, priority support." /> <x-pajak-form::radio name="plan" value="enterprise" label="Enterprise" description="Custom SLA, dedicated support." :disabled="true" />
<x-pajak-form::radio name="plan-err" value="free" label="Free" error="Please select a plan." /> <x-pajak-form::radio name="plan-err" value="pro" label="Pro" /> <x-pajak-form::radio name="plan-err" value="enterprise" label="Enterprise" />
Radio Card
A card-style radio button. Great for plan or option pickers where you need more visual weight than a plain radio.
<x-pajak-form::radio-card name="tier" value="free" label="Free" hint="Up to 3 projects, community support." :checked="true" /> <x-pajak-form::radio-card name="tier" value="pro" label="Pro" hint="Unlimited projects, priority support." /> <x-pajak-form::radio-card name="tier" value="enterprise" label="Enterprise" hint="Custom SLA and dedicated support." :disabled="true" />
<x-pajak-form::radio-card name="storage" value="10gb" label="10 GB" hint="Great for personal use." :checked="true"> <x-heroicon-o-circle-stack width="20" height="20" /> </x-pajak-form::radio-card> <x-pajak-form::radio-card name="storage" value="100gb" label="100 GB" hint="Ideal for teams and collaboration."> <x-heroicon-o-circle-stack width="20" height="20" /> </x-pajak-form::radio-card> <x-pajak-form::radio-card name="storage" value="1tb" label="1 TB" hint="For large-scale storage needs."> <x-heroicon-o-circle-stack width="20" height="20" /> </x-pajak-form::radio-card>
Toggle
A switch/toggle control. Uses a hidden checkbox as the underlying input, so it participates in forms normally. Three sizes: sm, md, lg.
@use('Pajak\Ui\Common\Enums\Size')
<x-pajak-form::toggle name="tog-sm" :size="Size::Sm" label="Small toggle" />
<x-pajak-form::toggle name="tog-md" label="Medium toggle (default)" :checked="true" />
<x-pajak-form::toggle name="tog-lg" :size="Size::Lg" label="Large toggle" />
<x-pajak-form::toggle name="tog-dis" label="Disabled" :disabled="true" :checked="true" />
<x-pajak-form::toggle name="tog-bare" /> <x-pajak-form::toggle name="tog-bare-on" :checked="true" /> <x-pajak-form::toggle name="tog-bare-dis" :disabled="true" />
<x-pajak-form::toggle name="tog-err" label="Enable notifications" error="You must enable notifications to continue." />
Repeater
A dynamic field group that lets users add and remove rows of inputs. Write plain field-key names inside the slot - the component automatically composes name, id, and for attributes into array notation for both server-rendered rows and dynamically added rows.
<x-pajak-form::repeater name="tags"> <x-pajak-form::input name="tag" placeholder="Tag" /> </x-pajak-form::repeater>
<x-pajak-form::repeater name="links" label="Links" :count="2" add-label="Add link"> <x-pajak-form::input name="label" label="Label" placeholder="Home" /> <x-pajak-form::url name="url" label="URL" placeholder="https://example.com" style="flex:2" /> </x-pajak-form::repeater>
<x-pajak-form::repeater name="emails" label="Email addresses" :min="1" :max="3" :count="1" add-label="Add email"> <x-pajak-form::email name="address" placeholder="[email protected]" /> </x-pajak-form::repeater>
Slider
A draggable range control. Supports single and range (two-thumb) modes, an optional value bubble, label, suffix, disabled state, and inline error message.
<x-pajak-form::slider name="volume" :value="40" />
<x-pajak-form::slider name="brightness" label="Brightness" :value="60" suffix="%" />
<x-pajak-form::slider name="quality" label="Quality" :value="75" suffix="%" :show-bubble="true" />
<x-pajak-form::slider name="price" label="Price range" :range="true" :value="20" :value-max="80" suffix="€" />
<x-pajak-form::slider name="vol-dis" label="Single (disabled)" :value="50" :disabled="true" /> <x-pajak-form::slider name="price-dis" label="Range (disabled)" :range="true" :value="25" :value-max="75" :disabled="true" />
<x-pajak-form::slider name="rating" label="Rating" :value="0" error="Please select a value greater than 0." />
File
A styled <input type="file">. Displays the selected filename inline. No JavaScript required.
<x-pajak-form::file name="attachment" label="Attachment" />
<x-pajak-form::file name="document" label="Document" accept=".pdf,image/*" placeholder="Select a PDF or image…" />
<x-pajak-form::file name="attachment" label="Attachment" error="The file must be smaller than 2 MB." />
<x-pajak-form::file name="attachment" label="Attachment" disabled />
Dropzone
A drag-and-drop upload zone with a file list. Supports single and multiple file modes. Works with standard HTML form submission - no async upload required. Pre-populate files via the :files prop for edit forms.
<x-pajak-form::dropzone name="documents" label="Documents" />
<x-pajak-form::dropzone name="contract" label="Contract" :multiple="false" />
<x-pajak-form::dropzone name="attachments" label="Attachments" accept=".pdf,image/*" />
@php
use Pajak\Ui\Common\Dto\UploadedFile;
$files = [
new UploadedFile(id: 42, name: 'contract.pdf', size: 214_000),
new UploadedFile(id: 43, name: 'invoice.pdf', size: 198_000),
];
@endphp
<x-pajak-form::dropzone name="documents" label="Documents" :files="$files" />
<x-pajak-form::dropzone name="documents" label="Documents" error="At least one document is required." />
@php
use Pajak\Ui\Common\Dto\UploadedFile;
$files = [
new UploadedFile(id: 42, name: 'contract.pdf', size: 214_000),
];
@endphp
<x-pajak-form::dropzone name="documents" label="Documents" :files="$files" disabled />
Avatar
A circular image upload with an initials fallback. Suitable for profile photos. Clicking the circle or the upload button opens the file picker. Standard form submission - no async upload.
<x-pajak-form::avatar name="avatar" label="Profile photo" initials="JK" />
<x-pajak-form::avatar name="avatar" label="Profile photo" initials="JK" src="https://i.pravatar.cc/200" />
<x-pajak-form::avatar name="avatar" label="Profile photo" initials="JK" error="Please upload a valid image." />
<x-pajak-form::avatar name="avatar" label="Profile photo" initials="JK" src="https://i.pravatar.cc/200" disabled />
Image Grid
A thumbnail grid for multiple image uploads. Selected images appear immediately as previews. Works with standard form submission. Pre-populate with existing images via the :images prop for edit forms.
<x-pajak-form::image-grid name="photos" label="Photos" />
@php
use Pajak\Ui\Common\Dto\UploadedFile;
$images = [
new UploadedFile(id: 10, name: 'receipt_1.jpg', url: 'https://picsum.photos/seed/r1/200/200'),
new UploadedFile(id: 11, name: 'receipt_2.jpg', url: 'https://picsum.photos/seed/r2/200/200'),
new UploadedFile(id: 12, name: 'receipt_3.jpg', url: 'https://picsum.photos/seed/r3/200/200'),
];
@endphp
<x-pajak-form::image-grid name="receipts" label="Receipt photos" :images="$images" />
<x-pajak-form::image-grid name="photos" label="Photos" error="At least one photo is required." />
@php
use Pajak\Ui\Common\Dto\UploadedFile;
$images = [
new UploadedFile(id: 10, name: 'receipt_1.jpg', url: 'https://picsum.photos/seed/r1/200/200'),
new UploadedFile(id: 11, name: 'receipt_2.jpg', url: 'https://picsum.photos/seed/r2/200/200'),
];
@endphp
<x-pajak-form::image-grid name="receipts" label="Receipt photos" :images="$images" disabled />