Collective Revived / laravelcollective/html


Form::open().Html::link().On Laravel 13.

The original package is abandoned and stops at Laravel 10. This one keeps the same Collective\Html namespace and the same output on Laravel 6 to 13 and PHP 8.0 to 8.5. Install it on the version you run today and keep it through every upgrade.

Terminal
$ composer require prabowosd/laravel-collective-html
  • Drop-in for 6.x
  • Laravel 6 to 13
  • PHP 8.0 to 8.5
  • MIT
01 / Install

Install

Shell
composer require prabowosd/laravel-collective-html

The service provider and the Form and Html facade aliases register through package discovery. Nothing to add to config/app.php.

The package declares replace for laravelcollective/html 6.x. If another dependency still asks for the original, Composer resolves it to this package instead of installing both, so the Collective\Html classes are only defined once.

02 / Migrate

Coming from laravelcollective/html

Namespace, class names, facades and helpers are the ones you already import.

  1. Remove the original packageIt is abandoned and does not install on Laravel 11 or newer, so it blocks your next upgrade.
    composer remove laravelcollective/html
  2. Require this oneSame Collective\Html classes, same rendered HTML.
    composer require prabowosd/laravel-collective-html
  3. Clear compiled viewsBlade directives such as @form_text live in cached views.
    php artisan view:clear

If you subclass FormBuilder or HtmlBuilder, check your overrides. Most methods now declare native return types such as HtmlString, and an override must declare a compatible one.

03 / Forms

Opening a form

Form::open() takes the target as url, route or action. Methods other than GET and POST are spoofed with a hidden _method field, and every non-GET form gets the CSRF token.

Blade
{!! Form::open([
    'url' => 'profile',
    'method' => 'PUT',
    'files' => true,
]) !!}
Output
<form method="POST"
      action="https://example.com/profile"
      accept-charset="UTF-8"
      enctype="multipart/form-data">
<input name="_method" type="hidden" value="PUT">
<input name="_token" type="hidden" value="…">
Blade
{!! Form::open(['route' => ['users.update', $user]]) !!}
{!! Form::open(['action' => [UserController::class, 'store']]) !!}

// Model binding: a field with no explicit value is filled from the model
{!! Form::model($user, ['route' => ['users.update', $user], 'method' => 'PATCH']) !!}

{!! Form::close() !!}
04 / Fields

Fields

Every field resolves its value in the same order: old input from the session, then the value you pass, then the bound model. A field gets an id when a Form::label() with the same name was rendered before it.

Blade
{!! Form::text('username', 'sdp', [
    'placeholder' => 'Username',
]) !!}
Output
<input placeholder="Username"
       name="username" type="text"
       value="sdp">
MethodSignature
text, email, tel, number, url, search, range, color, hidden($name, $value = null, $options = [])
date, time, datetime, datetimeLocal, month, weekSame. A DateTimeInterface value is formatted for the input type.
password, file($name, $options = []), never filled
textarea($name, $value = null, $options = []). 'size' => '50x10' sets cols and rows.
checkbox($name, $value = 1, $checked = null, $options = [])
radio($name, $value = null, $checked = null, $options = []), value defaults to the name
label($name, $value = null, $options = [], $escape_html = true)
submit, button($value = null, $options = [])
reset($value, $attributes = [])
image($url, $name = null, $attributes = [])
input($type, $name, $value = null, $options = []) for any other type

Boolean attributes go in as values without keys: ['required', 'autofocus'] renders required autofocus.

05 / Selects

Selects

Blade
{!! Form::select('size', [
    'L' => 'Large',
    'S' => 'Small',
], 'S') !!}
Output
<select name="size">
  <option value="L">Large</option>
  <option value="S" selected="selected">Small</option>
</select>

Nested arrays become <optgroup>s. Pass an array as the selected value for a multiple select, and a placeholder attribute for an empty first option.

Blade
{!! Form::selectRange('qty', 1, 10) !!}
{!! Form::selectYear('year', 2020, 2030) !!}
{!! Form::selectMonth('month') !!}   // month names in the app locale

From an enum

Backed enums use their value, pure enums the case name. A label() method on the enum supplies the option text, otherwise the case name is written as a headline.

Blade
{!! Form::enumSelect(
    'status',
    OrderStatus::class,
    'shipped',
) !!}
Output
<select name="status">
  <option value="pending">Pending</option>
  <option value="shipped" selected="selected">Shipped</option>
</select>

From Eloquent

The source can be a model class, a query or relation, a collection, or a plain array of rows.

Blade
{!! Form::modelSelect('category_id', Category::class, 'name', 'id', $selected) !!}
{!! Form::modelSelect('owner_id', $team->members(), 'full_name') !!}
06 / Validation

Validation helpers

These read the $errors bag Laravel shares with every view. Array names map to dot keys, so person[name] checks person.name. Classic calls are untouched: the invalid class is added only where you ask for it.

Blade
{!! Form::text('email', null,
    Form::withValidationClass('email', [
        'class' => 'form-control',
    ])
) !!}
{!! Form::error('email') !!}
Output, with an error
<input class="form-control is-invalid"
       name="email" type="text">
<div class="invalid-feedback">
  The email field is required.
</div>
Blade
@if (Form::hasError('email')) ... @endif
{{ Form::getError('email') }}   // first message, or null

Form::error() returns an empty string when the field is valid, so it is safe after every control.

07 / Components

Blade components

Label, control and error feedback in one tag. Attributes that are not props, such as placeholder, class or data-*, land on the control.

Blade
<x-form-input name="email" label="Email" type="email" placeholder="you@example.com" />
<x-form-input name="bio" label="Bio" type="textarea" rows="4" />

<x-form-select name="role" label="Role" :options="['admin' => 'Admin', 'member' => 'Member']" selected="member" />
<x-form-select name="status" label="Status" :enum="App\Enums\OrderStatus::class" />
<x-form-select name="category_id" label="Category" :model="App\Models\Category::class" label-column="name" />

<x-form-checkbox name="agree" label="I accept the terms" :checked="true" />
08 / Html

Html builder

Assets, links and small markup helpers. URLs go through Laravel's URL generator.

Blade
{!! Html::link('docs', 'Read the docs', [
    'class' => 'btn',
]) !!}
Output
<a href="https://example.com/docs"
   class="btn">Read the docs</a>
MethodOutput
script, style, favicon, image<script>, <link> and <img> tags for an asset path
link, secureLink, linkAsset, linkRoute, linkActionAnchors to a URL, asset, named route or controller action
mailto, email, obfuscateAddresses written as mixed HTML entities
ul, ol, dlLists from arrays
meta, tagAny element with attributes
entities, decode, nbspEscaping helpers

The global helpers link_to(), link_to_asset(), link_to_route() and link_to_action() are still there, each behind a function_exists check.

09 / Config

Classes are configuration

The validation helpers and components use Bootstrap class names by default. Publish the config to map them to Tailwind or anything else.

Shell
php artisan vendor:publish --tag=html-config
config/html.php
'classes' => [
    'control' => 'form-control',
    'select' => 'form-select',
    'label' => 'form-label',
    'invalid' => 'is-invalid',
    'feedback' => 'invalid-feedback',
    'check_wrapper' => 'form-check',
    'check_input' => 'form-check-input',
    'check_label' => 'form-check-label',
],
10 / Support

Support matrix

Every supported pair runs the full test suite in CI. From PHP 8.2 up, the suite also fails on any deprecation the package raises.

PHPL6L7L8L9L10L11L12L13
8.0testedtestedtestedtestedn/an/an/an/a
8.1n/an/atestedtestedtestedn/an/an/a
8.2n/an/an/atestedtestedtestedtestedn/a
8.3n/an/an/an/atestedtestedtestedtested
8.4n/an/an/an/an/atestedtestedtested
8.5n/an/an/an/an/atestedtestedtested

n/a marks a pair that Laravel itself does not support. Two features need more than the floor: the <x-form-*> components need Laravel 7, because class based Blade components arrived there, and Form::enumSelect() needs PHP 8.1, which introduced enums.