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

Semantic Form Laravel Package

laravolt/semantic-form

Laravel package for building Semantic UI forms with a fluent API, model binding, validation integration, and ready-made input components. It simplifies creating consistent, reusable forms for CRUD apps and admin panels.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire up (10 minutes).

composer require laravolt/semantic-form
php artisan vendor:publish --provider="Laravolt\SemanticForm\ServiceProvider"

Package auto-discovery registers the service provider and a SemanticForm facade alias. Publish the config and the views on day one — published views are your only real override surface and they are what you diff after every upstream bump.

The package emits markup only; it does not ship CSS/JS. Add Semantic UI (or Fomantic) to your layout before you judge anything visually:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fomantic-ui@2/dist/semantic.min.css">
<script src="https://cdn.jsdelivr.net/npm/fomantic-ui@2/dist/semantic.min.js"></script>

Where to look first, in order:

  1. config/semantic-form.php — template/view paths, error display behaviour, global defaults.
  2. resources/views/ inside the package (before publishing) — the actual field templates; this is where markup, error classes, and wrappers live.
  3. src/ — the builder class and the field classes. The fluent chain returns field objects, so every method you call lives on one of those; read it rather than guessing from the README.
  4. src/Macro* or the trait exposing macro() — the extension point for custom field types.

First use case. Pick one internal admin edit screen over a model you already have, and replace hand-written Blade with:

{!! SemanticForm::open()->route('users.update', $user)->method('PUT') !!}
{!! SemanticForm::model($user) !!}

{!! SemanticForm::text('name')->label('Full name')->required() !!}
{!! SemanticForm::email('email')->label('Email') !!}
{!! SemanticForm::select('role')->options($roles)->label('Role') !!}
{!! SemanticForm::textarea('notes')->label('Notes') !!}

{!! SemanticForm::submit('Save')->class('primary') !!}
{!! SemanticForm::close() !!}

That single screen tells you everything that matters: whether model binding covers your field types, whether old-input and error repopulation behave the way you want after a failed validation, and whether your layout's Semantic UI version agrees with the generated class names. Verify exact method signatures against src/ for the version you pin — the README lags the code.

Implementation Patterns

One form template for create and edit. The package's core value is that model binding is optional-by-null. Pass a fresh model on create and the populated one on edit; old input and validation errors take precedence over both, so the same Blade partial serves both routes:

public function edit(User $user) { return view('admin.users.form', ['user' => $user]); }
public function create()        { return view('admin.users.form', ['user' => new User]); }

Order matters. Open the form, then bind the model, then render fields. Binding after fields have been instantiated is the single most common "my values are empty" cause.

Wrap it, immediately. Do not let SemanticForm:: calls scatter across 40 Blade files. Put a thin layer in between — either Blade components or a small PHP form class per screen:

{{-- resources/views/components/form/text.blade.php --}}
{!! SemanticForm::text($name)->label($label ?? Str::headline($name))->value($value ?? null) !!}

The wrapper is what converts a vendor lock into a refactor, and it is where you encode team conventions (label casing, required markers, help text placement) once instead of per form.

Selects and relations: prepare options in the controller. Keep queries out of Blade. Pluck in the controller, pass an id→label array, and pass a default when editing so binding has something to match:

$roles = Role::pluck('name', 'id');
{!! SemanticForm::select('role_id')->options($roles)->placeholder('Choose a role') !!}

Enums/booleans: map them to a small array and reuse the same select/radio helper. Centralising the mapping in the wrapper prevents each developer inventing their own wording.

Macros for recurring composite fields. When you find yourself writing the same three-field address block five times, register a macro in a service provider's boot() and return a field object or a rendered segment. This is the sanctioned extension point and keeps call sites readable. Register macros in a provider that boots before views render, and keep the closure free of request state you might not have in queue/CLI contexts.

Validation errors are the payoff — lean on them. Let Laravel's $errors bag drive field-level error rendering (the package reads it per field name) rather than hand-writing @error blocks. If you use custom FormRequest attribute names, they flow through for free.

Grid layout: compose forms out of Semantic UI's fields/field grid by rendering field objects inside your own wrapper markup where the builder doesn't give you the grouping you want. Grouping is where you will drop to raw HTML soonest — that is expected and fine.

Testing: assert on rendered output of the form partial ($this->get(...)->assertSee('name="email"')), and add one test that a bound model's value appears. These catch silent markup changes on upgrade better than anything else.

Gotchas and Tips

  • Semantic UI is effectively unmaintained upstream; Fomantic is the practical successor. The generated markup is coupled to class names, so treat the CSS layer as an architectural dependency, not a detail. Validate Fomantic compatibility on your pilot screen before rolling out.
  • {!! !!} everywhere. Form builders return raw HTML. Never pass user-controlled data into label(), placeholder(), help(), or value() without escaping — the builder will not do it for you at every call site.
  • Old input wins over model binding, by design. That is what you want, but it means a failed-validation redirect must use withInput()/$request->flash() (Laravel does this automatically for validate() failures). If you redirect manually without flashing, the form silently reverts to the model value and looks like a binding bug.
  • Checkboxes and radios. An unchecked checkbox submits nothing; make sure your controller treats absence as false rather than leaving the old value. For checkbox groups, pass array-style names and confirm the builder's old-input handling for name[] matches — verify against src/, this is a classic mismatch area.
  • Nested/array field names (items[0][price]) are the weak spot. Binding and value lookup via dot notation is unreliable for repeated rows. Render per-row values explicitly with ->value($row->price) and keep the model binding for the flat fields only.
  • Multiple forms on one page can collide on error bags and CSRF-hidden-input placement. Namespace your error bags ($request->validateWithBag()) if you do this, or avoid it.
  • ->method('PUT')/PATCH must be set on open(); the builder emits the _method hidden input. Combine with @csrf — confirm the builder emits the CSRF field in your config; if not, add it explicitly.
  • Publish views before you customise anything. Editing vendor files means the next composer update silently wipes your work. After each bump, git diff the published views — 2.14.3 shipped with a changelog that is only a diff link, so the notes tell you nothing.
  • Laravel/PHP support lag. Last release 2022; the package touches Blade and validation internals. After every Laravel major, run your form-partial tests first — constraint conflicts surface in Composer, but behaviour changes surface only in rendered output.
  • Debugging trick: render a single field to a string ((string) SemanticForm::text('name')) inside tinker or a route closure. It is faster than bisecting a Blade template and shows you exactly which template and error class are in play.
  • Not the tool for API-first, Inertia, or Livewire front ends — the entire value is HTML generation, which those stacks bypass. If you only need a handful of inputs, a small set of hand-written Blade components is cheaper to own than a pinned, small-maintainer dependency plus its CSS framework.
  • Assign an owner. Escalation for a blocking bug is "pin the previous version and patch locally," which is fine for internal tooling and unacceptable without a named maintainer elsewhere.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource