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

Livewire Autocomplete Laravel Package

joshhanley/livewire-autocomplete

joshhanley/livewire-autocomplete adds a lightweight, customizable autocomplete input to Laravel Livewire apps. It supports asynchronous server-side search, model binding, and Blade integration, making it easy to build searchable selects and suggestion fields without extra JavaScript.

View on GitHub
Deep Wiki
Context7

Getting Started

joshhanley/livewire-autocomplete adds the behaviours a "type-ahead" input needs — debounced server search, an open/closed dropdown, keyboard navigation and a selection step — without you rebuilding that state machine in every component.

composer require joshhanley/livewire-autocomplete

Version 2.1.4 is the release to start on: it declares support for Laravel 13 and Livewire 4, which means the package is current with the framework versions you're most likely installing today. If you're still on Laravel 11/12 or Livewire 3, the same 2.x line works, but check composer.json constraints before assuming a downgrade is frictionless (see Gotchas).

Where to look first

  1. src/WithAutocomplete.php — the trait is the API. Everything the package does is exposed through it, so read it before the README; it's the authoritative list of properties, actions and the method names it expects your component to implement.
  2. README.md — the usage example is deliberately minimal. v2.1.4 added a section on running the test suite, which is worth reading because the package's own tests double as the best usage documentation.
  3. tests/ — the most accurate spec of how the trait behaves when the search term changes, when results are empty, and what happens on selection.

First use case: replace a <select> with 40+ options. A single-select "pick a user" input is the smallest thing that demonstrates the whole loop end to end.

use JoshHanley\LivewireAutocomplete\WithAutocomplete;
use Livewire\Component;

class UserPicker extends Component
{
    use WithAutocomplete;

    public string $search = '';

    public function render()
    {
        return view('livewire.user-picker', [
            'users' => User::query()
                ->where('name', 'like', "{$this->search}%")
                ->orderBy('name')
                ->limit(10)
                ->get(['id', 'name']),
        ]);
    }
}
<div>
    <input
        type="text"
        wire:model.live.debounce.300ms="search"
        autocomplete="off"
        placeholder="Search users…"
    >

    <ul>
        @foreach ($users as $user)
            <li wire:key="user-{{ $user->id }}" wire:click="select({{ $user->id }})">
                {{ $user->name }}
            </li>
        @endforeach
    </ul>
</div>

Match the exact property and action names against the trait in the version you install — the shape above is the contract, the identifiers are the part worth verifying.

Implementation Patterns

Keep the data source in render(), not in a public property. Results should be computed per-request from $search and passed to the view. Never store a collection of Eloquent models in a public property — Livewire serialises public properties into the payload on every round trip, and you'll ship the whole model graph to the browser.

Return id + label only. Project the columns you display (->get(['id', 'name'])) and prefer plain arrays or DTOs over models for the dropdown payload. It keeps the response small and stops you accidentally leaking attributes into the DOM.

Single-select with a hidden ID. The common integration is a belongsTo form field. Keep $search as the visible label and a separate $userId as the value; on mount(), seed $search with the existing relation's label so edit forms render correctly instead of showing an empty box.

Multi-select / tag input. Wrap the same trait with an array of selected items. On each selection, append to $selected and clear $search. Key the pills with wire:key so morphing doesn't shuffle them.

Extract before you duplicate. If more than one form needs autocomplete, put the trait in an abstract AutocompleteComponent base (or a Blade component for the markup) and let each concrete component supply its own query. The trait gives you behaviour; the query is the only genuinely per-case part.

Remote sources. When the results come from an HTTP API rather than the database, do the call inside the search path and cache aggressively on the term:

$results = Cache::remember("ac:{$this->search}", 300, fn () => $this->client->search($this->search));

Set a timeout on the client. A slow upstream turns every keystroke into a stalled Livewire request, and the debounce only helps so much.

Let Alpine own only what Livewire doesn't need. Click-outside dismissal and CSS transitions are fine in Alpine; the highlight index, selected value and result set should stay server-side so they survive re-renders and are testable.

Side effects go through the selection event. Rather than piggy-backing logic onto the click handler, listen for the event the trait dispatches after a selection and react there — that's the seam that keeps your component's behaviour independent of the markup.

Test the flow, not the DOM.

Livewire::test(UserPicker::class)
    ->set('search', 'jos')
    ->assertSee('Josh')
    ->call('select', $user->id)
    ->assertSet('userId', $user->id);

Add a case for "no results" and one for an unauthorised search — those are the two paths that break in production and never in the demo.

Gotchas and Tips

  • wire:model alone does nothing here. Livewire 3 and 4 defer model updates until the next request, so the dropdown will never populate as the user types. You need .live (usually .live.debounce.300ms). This is the single most common reason the package "doesn't work" on a fresh install.
  • Version pinning across the 2.1.4 boundary. 2.1.4 advertises Laravel 13 / Livewire 4 support. If your app is on Livewire 3 or Laravel 11/12, pin to the version you've tested rather than letting a composer update move you onto a release whose compatibility matrix you haven't verified. Check the published composer.json constraints, not just the changelog line.
  • wire:key on every result row. Without it, Livewire's DOM morphing reuses nodes by position and you get stale labels, mis-firing clicks and highlight state landing on the wrong row after the result set shifts.
  • Focus loss on re-render. Re-rendering the <input> itself can drop focus mid-typing. Avoid wire:key on the input, and if you're layering in a JS library use wire:ignore around it and drive it from Livewire events instead.
  • autocomplete="off". Browser autofill will happily paint its own dropdown on top of yours. It's a one-attribute fix that costs an hour of debugging if you miss it.
  • Unbounded queries. Always ->limit(n) and prefer prefix matching (like 'term%') over like '%term%' — the latter can't use a B-tree index and gets slower as the table grows. An autocomplete firing on every debounce tick is a very effective way to find out your users.name column is unindexed.
  • Public properties are public. Anything in a Livewire public property is visible to the client. Don't park API keys, internal IDs you wouldn't expose, or full models there.
  • Authorise inside the search path. Livewire methods are callable from the browser. If the component is reachable by users who shouldn't see the whole table, the Gate::authorize() / scoped query belongs in your component, not the route middleware.
  • Highlighting search terms is an XSS trap. Livewire escapes {{ }} for you. The moment you reach for {!! !!} to wrap matches in <mark>, escape the term first (e($search)) and only then inject the markup.
  • Debugging a stuck dropdown: check the network tab for the Livewire payload and confirm search is in it (if it isn't, it's the deferred-model issue above); then ->dump() inside the search path; then reproduce in Livewire::test() where you can see the full component state without the DOM in the way.
  • Extension points: the trait's methods can be overridden in your component, which is the supported way to customise selection or clearing without forking. If views are publishable in your version, publish them and keep the overrides in your app — that survives minor releases far better than a vendor patch.
  • Contributor note: the PHPUnit 12 attributes change in 2.1.4 only affects the package's own test suite. It matters if you're running the package tests locally or sending a PR; it has no bearing on your application.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo