Technical Evaluation
Framing correction (important for scope): despite the "Laravel/PHP package" framing, bootstrap-duallistbox is not a PHP package at all. It is a browser-side jQuery plugin (single JS file + CSS theme) that decorates a <select multiple> into a dual-list transfer widget. There is no composer.json, no service provider, no config file, no PHP API surface. The only Laravel touchpoint is asset delivery and the shape of the submitted form array. Any evaluation must treat it as a front-end dependency, with the "integration" work living in Blade, the asset pipeline, and validation rules.
Architecture fit
- Fits cleanly only where the app already ships jQuery + Bootstrap 3/4-era markup. In a Vite + Livewire/Alpine/Stimulus stack it is an architectural outlier that pulls jQuery back into the bundle for one widget.
- Zero server-side coupling is actually a mild plus: the server contract is just
name="roles[]" → array validation. No ORM, queue, cache, or auth concerns.
- No TypeScript definitions, no ESM/UMD-first build, no SSR path. It assumes a real DOM and a global
$.
Integration feasibility
- Mechanically easy: vendor the minified JS/CSS into
resources/, import from resources/js/app.js, or copy to public/vendor/ via a publish step. A ~10KB asset with a documented options object.
- The real work is in lifecycle management, not installation: the plugin mutates the DOM (hides the original select, injects two selects and buttons), so it must be explicitly
refresh()-ed when options change server-side and destroy()-ed before Livewire/Turbo morphs the node, or you get duplicated widgets.
- Server-side validation must be written against the original field name; the plugin's value-syncing behaviour needs verification against Laravel's
array/exists rules before committing.
Technical risk
- Staleness: last release 2020-04-25; ~5+ years without a commit. Effectively maintenance-mode/abandoned. No security response process.
- License ambiguity:
NOASSERTION means the LICENSE file is non-standard or unparseable by tooling. Legal review is a hard gate — this is a blocking item, not a footnote.
- Bootstrap 5 incompatibility risk: BS5 dropped jQuery and changed utility/markup conventions; the bundled themes target BS3/BS4. Expect CSS rework, not a drop-in.
- Accessibility and mobile UX are unverified: dual-list widgets are notoriously poor for screen readers and touch; no evidence of ARIA conformance testing.
- XSS surface: if option labels are rendered from user-controlled data, verify the plugin escapes on insert rather than assuming it does.
- No tests, no CI, no changelog cadence, no maintainer SLA, single-maintainer bus factor.
Key questions
- What is the actual license text, and does it permit commercial redistribution in a bundled SPA?
- Which Bootstrap version and jQuery version is the target app on today, and for the next 12 months?
- Is the dual-listbox a hard product requirement, or is "multi-select with search" sufficient (which a maintained library like Tom Select or Choices.js satisfies without jQuery)?
- Does the flow need dynamic option loading (dependent selects) or Livewire reactivity? If yes, the lifecycle cost rises sharply.
- Who owns front-end dependency hygiene, and will they accept an unmaintained dependency for a user-facing control?
Integration Approach
Stack fit
- Requires jQuery 3.x as a peer dependency and a Bootstrap 3/4 stylesheet. If the app is jQuery-free, adopting this package means re-introducing jQuery globally for one form control — a poor trade against a jQuery-free alternative.
- Best fit: legacy Laravel app on Bootstrap 4, Blade-rendered forms, minimal or no reactive front-end. Worst fit: Inertia/Vue/React SPA, Livewire-heavy admin, or a BS5 design system.
Migration path (if adopted)
- Vendor, don't CDN. Pin the exact 2020 artifact into the repo, record the source URL and checksum, and add a comment block with provenance. Do not rely on a third-party CDN for a user-facing control.
- Wrap, don't sprinkle. Encapsulate in one Blade component (
<x-dual-listbox>) plus one small JS init module that owns init, refresh, and destroy. Every call site uses the component; no ad-hoc $('.selector').bootstrapDualListbox() in views.
- Bind to the framework lifecycle. Register
destroy() on livewire:navigating / turbo:before-cache and re-init on livewire:navigated / turbo:load. Idempotency guard to prevent double-init.
- Lock the server contract first. Decide the wire format (
roles[] array), write the FormRequest rules and feature test before styling, so the widget is swappable later without touching controllers.
- Keep the exit door open. Isolate the widget behind a single Blade component with a stable prop signature so the underlying library can be replaced with Tom Select/Choices.js without touching any view.
Compatibility checks to run in the spike
- Bootstrap version rendering of buttons, filter input, and responsive stacking.
- jQuery version conflicts with any existing plugins.
- Behaviour with
wire:model, old() repopulation, and validation-error redisplay (does selection survive a 422 round-trip?).
- Native form reset and browser back/forward cache restoration.
Sequencing
License review → 2-day spike on one real form (BS/jQuery compatibility + a11y + mobile) → decision gate: adopt vs. replace with maintained alternative → if adopt: component wrapper + feature test + provenance doc → rollout form-by-form, not app-wide → schedule a 12-month re-evaluation because upstream is frozen.
Recommendation framing: this is a conditional, tactical adoption — acceptable as a short-lived bridge in a legacy BS4/jQuery app, not acceptable as a new dependency in a greenfield or BS5 codebase. Default bias should be replacement.
Operational Impact
Maintenance
- Zero upstream maintenance expected. All future fixes — BS5 migration, a11y patches, security issues — are your team's cost, on a codebase you did not write and that has no test suite.
- Vendored copy means no surprise breakage, but also no CVE notifications; it will not appear in
composer audit or npm audit in a meaningful way if vendored as a static file. Add it to the dependency inventory manually.
- Single-maintainer project with a five-year gap since last release: treat as archived.
Support
- No vendor, no SLA, no paid tier, no active issue triage. Internal ownership must be explicitly assigned (a named front-end owner) or it becomes orphaned code in a form nobody wants to touch.
- Onboarding docs needed: a short README on the wrapper component, the init/destroy contract, and the known Bootstrap caveats — otherwise the next developer re-inits it in a view and reintroduces the double-widget bug.
Scaling
- Runtime cost is negligible: client-side DOM manipulation on a bounded option list, no network chatter beyond initial asset load.
- The real scaling limit is option count and payload: dual listboxes degrade badly past a few hundred options, and the plugin renders all options in both panes. Large permission/role sets will need server-side pagination or search endpoints — which the plugin does not provide. Plan for a different control if the data is unbounded.
- Asset weight is small; the bundle cost is jQuery, not this plugin.
Failure modes to design against
- Double initialization after Livewire/Turbo navigation → duplicated panes and lost selection.
- Stale options when the source list changes server-side without
refresh().
- Silent validation mismatch — the field submits under a different name/shape than the FormRequest expects, producing "the role field is required" on a visibly filled control.
- Selection loss on 422 redisplay if
old() isn't rehydrated into the select before init.
- Mobile/touch and screen-reader failure — untested, likely degraded; needs an accessible fallback (plain multi-select) or explicit sign-off.
- Design drift when the app upgrades Bootstrap; the widget will look broken before it behaves broken.
Ramp-up
- Low technical ramp-up (a few hours to competence; options are well-named), but a non-trivial organisational ramp-up: license sign-off, ownership assignment, provenance documentation, and a documented replacement path.
- Budget roughly: ½ day license/legal, 1–2 days spike and compatibility verification, 1–2 days component wrapper + tests, ongoing ~0 upstream support but a recurring internal tax each Bootstrap/design-system upgrade.
Net TPM read: low implementation cost, high long-tail ownership cost. Approve only as an explicitly temporary bridge with a named owner, a pinned vendored artifact, and a dated review date; otherwise redirect to a maintained jQuery-free alternative and spend the same integration effort once instead of twice.