axiolab/bs-select-bundle
A Symfony bundle that integrates Bootstrap Select into your forms, adding a ready-made form type and Twig rendering for searchable, styled dropdowns with minimal configuration.
First, a reality check. This is a Symfony bundle, not a Laravel package. It has no service provider, no config/bs-select.php, and nothing that registers with Laravel's container. It works by hooking Symfony's Form component — overriding the choice widget renderer so a ChoiceType/EntityType emits the markup and assets that bootstrap-select expects. There is no composer require path from a Laravel app to a working select. The value here is the pattern, not the code.
Two paths, pick one up front:
config/bundles.php and a form_themes entry pointing at the bundle's Twig namespace.Where to look first in the repo, in this order:
README.md — for the intended type name and asset setup. Given zero stars and zero dependents, assume it's thin or stale.Resources/views/Form/ — the form theme. This is the whole trick: it overrides choice_widget / choice_widget_collapsed to add class="selectpicker" and data-* attributes.Form/Type/ — the custom type that presumably extends ChoiceType.Resources/public/ or composer.json — whether bootstrap-select itself is vendored, or you're expected to provide it.symfony/* constraints in composer.json before investing anything.First use case, either path: a single <select> with 100+ options (countries, categories, users) that needs typeahead, a "select all" action, and multi-select — which native <select multiple> handles poorly. Bootstrap-select solves exactly that and nothing more.
In Laravel, the minimal port:
{{-- resources/views/components/bs-select.blade.php --}}
@props(['name', 'options' => [], 'selected' => null, 'multiple' => false, 'search' => true])
<select
name="{{ $name }}{{ $multiple ? '[]' : '' }}"
@if($multiple) multiple @endif
{{ $attributes->merge(['class' => 'form-select selectpicker']) }}
@if($search) data-live-search="true" @endif
>
@foreach($options as $value => $label)
<option value="{{ $value }}"
@selected(in_array((string) $value, array_map('strval', Arr::wrap(old($name, $selected ?? [])))))>
{{ $label }}
</option>
@endforeach
</select>
Then one initializer, run once:
// resources/js/bs-select.js
import 'bootstrap-select/dist/css/bootstrap-select.min.css';
import 'bootstrap-select';
const init = (root = document) =>
root.querySelectorAll('.selectpicker:not(.initialized)')
.forEach(el => window.jQuery(el).selectpicker().addClass('initialized'));
document.addEventListener('DOMContentLoaded', () => init());
The three patterns the bundle exposes (and what they map to in Laravel):
Custom form type. Use the bundle's type instead of ChoiceType — typically something in the axiolab\BsSelectBundle\Form\Type namespace. This is the cleanest entry point if it exists, because it carries the attr defaults for you. In Laravel the analogue is the Blade component above: a named, reusable widget rather than sprinkling class="selectpicker" across views.
Global form theme vs. per-form theme. Symfony lets you register a theme globally (twig.form_themes) or scope it with {% form_theme form 'BsSelectBundle:Form:bs_select.html.twig' %}. Prefer the scoped version while evaluating an unmaintained bundle — a broken theme override applied globally will mangle every form in the app. In Laravel, scope is implicit: only views that use <x-bs-select> are affected. That's a genuine advantage of the port.
Attribute-driven configuration. Almost all behavior is data-* on the select: data-live-search="true", data-actions-box="true" (select all / deselect all), data-selected-text-format="count > 3", data-width="100%", data-size="10". If the bundle's type doesn't expose these as options, pass them through attr. Do this in attr, not in the theme — data-* in a form theme is where maintenance pain lives.
Day-to-day workflow that actually holds up:
data-live-search. Straightforward, no server involvement.BelongsToMany / multi-select relations: multiple => true plus data-actions-box="true". Name the field roles[]; validate with 'roles' => ['array'], 'roles.*' => ['exists:roles,id']. The transform is purely client-side, so the underlying <select multiple> still posts an array — nothing changes in your FormRequest.old() must be applied at render time (as in the component above) and the plugin must be initialized after the DOM reflects it. Order matters: render → init, never init → render.hook / onload / Turbo turbo:load handler, and call .selectpicker('refresh') after any option list mutation. This is the single most common source of "it works on first load, then stops."Integration tips:
@vite ordering across separate tags.:not(.initialized) guard) so partial re-renders don't double-wrap.Signal quality. Zero stars, zero dependents, empty description grammar. Treat this as a reference implementation to read, not a dependency to install. There is no community, no Stack Overflow corpus, no issue triage. Pin an exact version if you vendor it, and be prepared to fork. Before anything else, check the last commit date and whether the symfony/form constraint allows your version — a bundle that overrides choice_widget is tightly coupled to the theme block names, which changed between Bootstrap 3/4/5 layouts.
Double-styling with Symfony's own Bootstrap form themes. If you already use bootstrap_4_layout.html.twig or bootstrap_5_layout.html.twig, those already style selects. Layering bootstrap-select on top produces a styled <select> and a styled replacement button. Register the bundle theme with higher priority, or explicitly disable the conflicting blocks — don't assume the bundle wins.
$(...).selectpicker is not a function means the plugin script never executed. Causes, in order of likelihood: jQuery loaded twice (a second copy scoped to a module), bootstrap-select loaded before jQuery, or the script was tree-shaken out of the Vite build because it was only imported for side effects without a CSS/import reference. Check window.jQuery.fn.selectpicker in the console.
selectpicker('refresh') is mandatory, not optional. Any time you change options, toggle disabled, or re-render a Livewire component, the visible widget is stale until you refresh. Wire it into a single helper and call it from one place; scattered calls rot fast.
Native select is still the source of truth — good. Since the plugin hides, but does not remove, the <select>, server-side validation, old(), and $request->validate() all work unchanged. Don't be tempted to validate against whatever the plugin's DOM shows.
Accessibility regression. bootstrap-select replaces the control with a <button> plus generated divs. Screen-reader semantics are weaker than a native <select>, and keyboard navigation has known quirks (particularly inside modals where Bootstrap traps focus). If your app has an accessibility requirement, this is a reason to prefer a native select with <datalist> or a plain search input, or to pick a plugin with better a11y support.
Modals and dropdown clipping. A select inside a Bootstrap modal will have its menu clipped by overflow: hidden on the modal body. Set data-container="body" on the select, and expect z-index fights with the modal backdrop.
Extension points worth knowing if you stay on Symfony: override the choice_widget_collapsed block rather than choice_widget, so expanded => true (radio/checkbox) forms keep rendering normally. A FormTypeExtension is usually cleaner than a custom type — it lets ChoiceType itself gain the behavior instead of forcing call sites to remember a different class name. In Laravel, the equivalent lever is a Blade component class plus a @once @push('scripts') for the initializer, keeping the per-view footprint to one tag.
Debugging recipe. View source, not devtools-inspect: confirm the <select> actually carries selectpicker and your data-* attributes. If they're absent, the theme/component never applied — on Symfony run bin/console debug:twig | grep -i select, on Laravel check that the component is resolved from the right namespace. If the attributes are present but nothing renders, it's a JS problem, not a template problem. Those are separate failure modes; diagnose them separately.
How can I help you explore Laravel packages today?