Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

X Form Laravel Package

vkm-apps/x-form

vkm-apps/x-form is a Laravel package for defining and rendering dynamic forms from configurable schemas. It helps you build reusable fields, manage validation, and streamline form submission for admin panels and custom applications.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Verify compatibility before you composer require anything.

The release notes contain a contradiction you must resolve first: v2.0.0 says "Support Laravel 13 & Livewire 3. To use Livewire 3 use v1". Don't guess — open composer.json on the exact tags in the repo (v2.0.9, v1.1.5) and read the require block for laravel/framework, livewire/livewire, and php. Then pin an exact tag in composer.json, not a caret range:

"require": {
    "vkm-apps/x-form": "2.0.9"
}

With 0 stars and 0 dependents there is no downstream project to surface a breaking change for you. Pinning is the only safety net.

2. Confirm the stack assumptions hold. This package is Blade + Livewire + AlpineJS. If your frontend is Inertia/Vue/React, stop here — you'd be wrapping components until you've built your own layer with an extra dependency attached.

3. Read these three places in the vendor directory first:

  • config/x-form.php — the CSS class map (refactored in v1.0.5; it drives radio/checkbox alignment and field styling). This is your styling lever, not CSS overrides.
  • resources/views/components/ — the actual <x-form.*> templates. With only release notes published, the source is the documentation.
  • src/ — look for the validation/error rendering path, specifically any {!! $message !!} (see Gotchas).

4. First use case: a settings form. Pick a low-blast-radius screen — an internal settings page or a profile form — and prove the binding contract works:

{{-- resources/views/livewire/settings-form.blade.php --}}
<form wire:submit="save">
    <x-form.input model="name" label="Display name" />

    <x-form.password model="password" label="Password" />

    <x-form.checkbox model="notifications" label="Email me updates" />

    <x-form.date model="starts_on" label="Start date" />

    <button type="submit">Save</button>
</form>
// app/Livewire/SettingsForm.php
class SettingsForm extends Component
{
    public string $name = '';
    public string $password = '';
    public bool $notifications = true;
    public ?string $starts_on = null;

    protected $rules = [
        'name'          => 'required|max:120',
        'password'      => 'nullable|min:12',
        'starts_on'     => 'nullable|date',
    ];

    public function save() { $this->validate(); /* ... */ }
}

The key contract: model="foo" binds to a public Livewire property the same way wire:model does. That single attribute is the whole integration surface — once it works for one field, it works for the vocabulary.

Implementation Patterns

Bind through model, validate in the component. The package renders and displays errors; it does not own your rules. Keep $rules on the Livewire component so validation stays testable and greppable, and let the component render the error state. This is the pattern that actually pays off — one error presentation across every form instead of eight slightly different ones.

Date ranges return an array. The range attribute generates two inputs and hands back [start, end] to your property. Handle it explicitly rather than storing a scalar:

<x-form.date model="period" label="Reporting period" range live />
public array $period = ['', ''];

protected $rules = [
    'period.0' => 'nullable|date',
    'period.1' => 'nullable|date|after_or_equal:period.0',
];

The live attribute (added in v2.0.3) gives you reactive updates — useful when the second date's validity depends on the first, but be aware every keystroke round-trips to the server. Use live on the fields where the value drives conditional UI, not blanket.

One editor per bound property, keyed loops for dynamic arrays. <x-form.editor> is the highest-value and highest-risk component: it's a rich-text editor with file manager and YouTube insert tied to the vkm-js bundle. The changelog shows repeated fixes here — table ID conflicts (v2.0.1), file manager loading after Livewire updates (v1.1.0), dynamic arrays (v1.1.3). When you loop editors, add wire:key:

@foreach($sections as $index => $section)
    <div wire:key="section-{{ $section['id'] }}">
        <x-form.editor model="sections.{{ $index }}.body" />
    </div>
@endforeach

Then actually test two editors on one page after any Livewire upgrade. That is the documented failure mode, and it fails silently (a second editor renders with the first's ID).

Checkbox and radio groups are horizontal by default. Since v1.0.5, radios and checkboxes align horizontally unless you set horizontal="false". If your design system expects a vertical stack in a sidebar, that's a per-use attribute, not a config change — resist editing published views for layout.

Centralize overrides in config, not published views. php artisan vendor:publish for x-form gives you config and views. Take the config (class maps), skip the views unless you need structural change. Published views become a fork you maintain on every upgrade; a class map override does not.

Wrap once for label/help/error markup. Rather than sprinkling attributes, define a thin local wrapper if you need consistent label/help semantics:

{{-- resources/views/components/field.blade.php --}}
<div class="field">
    <x-dynamic-component :component="'form.'.$type" :model="$model" :label="$label" />
    @error($model) <p class="field-error">{{ $message }}</p> @enderror
</div>

This also gives you one place to enforce escaped error output — see Gotchas.

Test at two levels. Livewire's Livewire::test() covers binding, rules, and array indexing. But the bugs this package has historically shipped are browser-level (multi-editor IDs, file manager post-update, dynamic arrays). Budget a handful of Dusk/browser tests for those exact scenarios.

Gotchas and Tips

The documented XSS vector is the first thing you address, not the last. Release v2.0.1 states plainly that error messages render via {!! $message !!}, allowing custom HTML. That means any code path where untrusted input reaches an error string is an injection point. Concretely:

  • Never build error messages from user input ('Invalid value: ' . $request->input('x') is now an XSS).
  • Prefer static messages; if dynamic, escape before passing.
  • Add a review checklist item on every form PR.
  • If you're forking (and you probably should be), consider replacing the unescaped render with {{ $message }} plus a separate opt-in slot for trusted HTML.

Treat this as a launch gate before any customer-facing form, not a backlog ticket.

Resolve the Livewire version question in writing before committing. "Support Laravel 13 & Livewire 3. To use Livewire 3 use v1" cannot both be true. Read composer.json on each tag, test your target version, and record the answer in your repo. This determines your entire upgrade path.

Re-publish config on major upgrades. v1.0.5 "refactored x-form config css classes." If you carry a v1 config file into v2, class names may silently not apply — fields render unstyled rather than erroring. Diff the shipped config against yours at every major bump.

ValidateDropdown was removed in v1.0.24 ("to be added in a future release as standalone"). Don't plan a dependent form around it, and don't assume it exists because you saw it referenced in an older example.

Debug a non-binding field by checking the attribute name. The most common mistake is reaching for wire:model out of habit — the component expects model. If a field doesn't update, check that first, then check that the Livewire property is public and not protected/typed incompatibly (e.g. ?string bound to a date range array).

Editor re-render issues usually trace to the Livewire lifecycle, not the package. After $refresh, a wire:model.live cascade, or a modal open, the editor's underlying JS may need re-initialization. The changelog shows this being patched repeatedly (v1.1.0, v1.1.3, v2.0.1) — so if you hit it, check whether your version already includes the fix before writing your own workaround, and prefer wire:ignore around the editor wrapper over patching the package.

Assume no support and plan for ownership. Zero stars, zero dependents, single vendor. Before this touches a revenue-critical form: name an internal maintainer, vendor the package (or fork under MIT), pin the tag, and subscribe to releases. Budget upgrade hours per Laravel/Livewire bump as a real line item — the repeated editor/ID/file-manager fixes are evidence that upgrades are not drop-in.

Extension points to prefer, in order: config class maps → local Blade wrapper → published views → fork. Each step down costs more at upgrade time. Extensibility (custom field types, macros, renderers) is described in the package summary but undocumented — verify in src/ before promising it to a teammate.

Watch for Alpine collisions. If your layout already defines x-data on an ancestor, a form component's Alpine state can conflict. Scope x-data tightly and inspect the rendered DOM before assuming a component is broken.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log