smirik/bootstrap-bundle
Symfony bundle that integrates Bootstrap into Symfony applications, providing Twig helpers, form themes, and templates for common UI components. It simplifies building responsive forms, menus, alerts, and navigation with consistent Bootstrap markup while reducing repetitive frontend boilerplate.
First, the reality check. This is a Symfony bundle (Smirik\BootstrapBundle), not a Laravel package. You cannot composer require it into a Laravel app — the service-container extension, bundle registration in AppKernel.php, and Twig form-theme conventions have no counterpart in Laravel. If you're on a modern Symfony (5/6/7) app, the framework already ships bootstrap_5_layout.html.twig, so you don't need it either.
What you actually do with this package day-to-day: treat it as reference material. It contains two reusable things worth reading: the Bootstrap asset include and the form theme. You port them.
Read in this order:
Resources/public/ — the vendored Bootstrap CSS/JS. Check the version first (this predates BS4/5; expect BS2/3 class names like control-group, has-error, help-block).Resources/views/Form/ (form_div_layout.html.twig overrides) — the actual value: how each form_row/form_widget/form_errors block is wrapped.Resources/views/layout.html.twig — the block naming and asset include pattern.DependencyInjection/ and Resources/config/ — to see whether anything is configurable (usually not much).First use case — port the form theme to Blade. The Symfony block looks roughly like:
{% block form_row %}
<div class="form-group{% if errors|length %} has-error{% endif %}">
{{ form_label(form) }}
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
{% endblock %}
The Laravel equivalent is a Blade component in resources/views/components/form/field.blade.php:
@props(['label' => null, 'name', 'error' => null])
<div class="mb-3 {{ $error ? 'has-error' : '' }}">
@if($label)
<label for="{{ $name }}" class="form-label">{{ $label }}</label>
@endif
{{ $slot }}
@error($name)
<span class="form-text text-danger">{{ $message }}</span>
@enderror
</div>
That is the whole package's value, reproduced in your own repo in under an hour — and under your own review.
1. Asset loading — skip the bundle's approach, use Vite. The bundle's job is to emit <link>/<script> for Bootstrap. In Laravel that's your build pipeline:
// resources/js/app.js
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
Then reference it once in the base layout: @vite(['resources/js/app.js']). The bundle's Resources/public convention is the Symfony Assetic-era equivalent — don't try to reproduce it.
2. Form field rendering as components. The most useful pattern to carry over is the "wrap label + widget + errors in a fixed container" convention. Build one <x-form.field> wrapper plus thin per-type components (<x-form.input>, <x-form.select>, <x-form.checkbox>). Keep the markup in one place so a Bootstrap major upgrade is a single-file change.
3. Error display convention. The bundle consistently treats errors as a sibling of the input (not nested), with an has-error wrapper class. In Blade/BS5 the equivalent is:
<input class="form-control @error('email') is-invalid @enderror" ...>
@error('email')<div class="invalid-feedback">{{ $message }}</div>@enderror
Establish this convention once; the bundle's discipline here is worth borrowing even if the class names aren't.
4. Layout/block convention. The bundle ships a base layout you extend with {% block body %}. The Blade analogue is a layouts/app.blade.php with @yield('content') or $slot. If you're already standardizing layouts, this is where the reference is most useful — compare your block structure to what the bundle chose (it's a reasonable, if old, starting point for admin screens).
5. If you're genuinely on Symfony. Don't port it — register the built-in theme instead:
twig:
form_themes:
- 'bootstrap_5_layout.html.twig'
That's first-party, maintained, version-locked with the framework. The bundle is essentially this same idea, frozen in 2013.
control-group → form-group → mb-3, help-block → form-text, has-error → is-invalid). Copying markup verbatim into a BS5 app will produce visually broken but functionally "passing" forms — the failure mode is cosmetic, so tests won't catch it.form_errors is a separate block from form_widget. In a partial port, it's easy to render both form_errors(form) inside form_widget and again inside form_row, producing duplicated error messages. If you port, keep the error rendering in exactly one place.twig.form.resources entries override earlier ones — overriding only a few blocks lets the rest fall through to a base theme you may not expect. If porting to Symfony, check what form_div_layout.html.twig is still rendering for untouched blocks.assets:install --symlink and assetic:dump fail silently on hardened CI (symlink restrictions), Windows developer machines, and Docker volumes. If you find yourself debugging "why are assets not updating," the answer is usually a stale symlink — assets:install --symlink --relative or a hard copy avoids it. Don't bother resurrecting Assetic; it's dead on both Laravel and modern Symfony alike.Extension class and Resources/config/*.yml will tell you in 30 seconds whether there's a knob worth looking at. Usually there isn't.layout.html.twig references paths under Resources/public, you'll need assets:install before anything renders — but once ported to Blade/Vite, this whole class of gotcha disappears. That's the argument for porting rather than depending.Bottom line for a Laravel team: you will never require this. Extract the form theme (one file), translate class names to your Bootstrap major, and move on. The day-to-day leverage is small and one-time, but the form-theme pattern is worth a look before you write your own.
How can I help you explore Laravel packages today?