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

Select Components Laravel Package

akhmads/select-components

akhmads/select-components offers Laravel Blade components for building select inputs, including searchable dropdowns, dependent selects, and customizable option styling. Drop them into your views with minimal setup and no extra JavaScript build steps required.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm the version floor first. This package is at v1.x with a single-line release note ("Added support for Laravel 13"), so the one thing that can block you before any code is written is the supported matrix.

composer require akhmads/select-components

Before you plan any work around it, read the installed composer.json:

composer show akhmads/select-components
# then inspect the require block
cat vendor/akhmads/select-components/composer.json

Check laravel/framework and php constraints. If the Laravel 13 bump also raised the Laravel or PHP floor, you need to know that before you spend a day building forms on top of it. Pin the exact version ("1.1.0", not ^1.1) until you have a track record with it.

Where to look first, in this order:

  1. vendor/akhmads/select-components/composer.json — version matrix, autoload, extra.laravel.providers.
  2. vendor/akhmads/select-components/resources/views/ — this is where the actual API lives. The Blade files define the props, slots, and attribute handling more reliably than any README.
  3. vendor/akhmads/select-components/config/ — the config keys you will be publishing.
  4. src/ — the component class names, which tell you the tag names (<x-select />, <x-select-components::select />, or similar).

Publish the config and views.

php artisan vendor:publish --provider="Akhmads\SelectComponents\SelectComponentsServiceProvider"

If the provider name differs, use php artisan vendor:publish interactively — it will list the package's tags. Prefer publishing config only at first; published views are a snapshot that will not receive upstream fixes, and you want to know you need to customize before you take that on.

First use case. Start with the boring one: a server-rendered select in a standard CRUD form. No AJAX, no search, no multiple. Something like:

<x-select name="status" :options="['draft' => 'Draft', 'published' => 'Published']" :selected="old('status', $post->status)" />

Verify three things on this single component before scaling up:

  • Does old() rehydration work after a failed validation redirect?
  • Does it render the name and id attributes you expect, so <label for> and $request->validate() line up?
  • Does it pass through arbitrary attributes (class, required, disabled), or does it swallow them?

That last one determines how much fighting with styling and accessibility you will do later. It is the highest-value five minutes you will spend on this package.

Implementation Patterns

Wrap it once, use it everywhere. This package is a small Blade component set with one maintainer and one-star traction. The exit cost matters more than the entry cost. Create a thin application-level component that delegates to the package:

{{-- resources/views/components/field/select.blade.php --}}
@props(['name', 'options' => [], 'selected' => null, 'label' => null])

<div class="form-field">
    @if ($label)
        <label for="{{ $name }}">{{ $label }}</label>
    @endif

    <x-select
        :name="$name"
        :id="$name"
        :options="$options"
        :selected="old($name, $selected)"
        {{ $attributes->merge(['class' => 'select-control']) }}
    />

    @error($name)
        <p class="form-error">{{ $message }}</p>
    @enderror
</div>

Now if you replace the package, or it stops getting updates, you rewrite one file instead of grepping 60 views. This is the single most important pattern in this assessment.

Static options vs. model-backed options. Keep option construction out of the Blade file. Two clean shapes:

// app/Support/Options.php
class Options
{
    public static function fromModel(string $model, string $label = 'name'): array
    {
        return $model::query()->orderBy($label)->pluck($label, 'id')->all();
    }
}
<x-field.select name="author_id" :options="\App\Support\Options::fromModel(\App\Models\User::class)" :selected="$post->author_id" label="Author" />

For relationships, always eager-load and pluck — an unguarded $user->roles inside an options loop is a classic N+1 that only shows up when the page has 40 selects or 4,000 rows.

Multiple selection. The main trap is that Laravel's old() and validation semantics differ from single select.

<x-select name="tags[]" multiple :options="$tagOptions" :selected="old('tags', $post->tags->pluck('id')->all())" />
$request->validate([
    'tags'   => ['array'],
    'tags.*' => ['integer', 'exists:tags,id'],
]);

Confirm with a browser test that the component actually emits name="tags[]" (or tags with multiple) matching what your validation expects — mismatched naming between component and validator is the number-one cause of "validation passes but nothing saves."

Dependent / cascading selects. The package advertises AJAX-driven cascades. Whatever the exact API, structure it the same way you would with vanilla JS:

  1. A JSON endpoint per level, authorized with a policy check, not just auth middleware:
Route::get('/api/cities', function (Request $request) {
    $request->validate(['region_id' => ['required', 'integer']]);
    return City::where('region_id', $request->integer('region_id'))
        ->orderBy('name')
        ->get(['id', 'name']);
})->middleware('auth');
  1. The parent select carries a data attribute for the child's target; the child starts disabled with a placeholder.
  2. On edit forms, rehydrate the full chain on page load. Cascades routinely ship broken in this exact place: the city value is set, the region select never fires its change event, so the user sees a blank city and thinks their data was lost. Test the edit path, not just the create path.

Searchable dropdowns need a server-side ceiling. Client-side filtering over 200 options is fine. Over 5,000 it is a frozen tab. If the package's search is client-side only (likely, given "searchable, filterable" with no mention of remote search), cap the options you render and push anything larger to an async lookup endpoint of your own. Design the cap in on day one — retrofitting it means changing the component's contract.

Testing. Don't unit-test the package; test your wrapper and your option-building.

public function test_post_edit_form_preselects_status(): void
{
    $post = Post::factory()->create(['status' => 'published']);

    $this->actingAs($post->author)
        ->get(route('posts.edit', $post))
        ->assertOk()
        ->assertSee('value="published"', false);
}

And for the cascade, one end-to-end test that loads the edit page and asserts the child's pre-selected value is present in the markup. That single test has caught more regressions in component-based forms than any amount of PHPUnit coverage of the package itself.

Livewire note. If you use Livewire, check whether the package passes through wire:model cleanly. Blade components that hard-code attributes can drop wire:* directives. Verify with one spike component before committing to the pattern across a module.

Gotchas and Tips

The release note tells you what was added, not what was dropped. v1.1.0 advertises Laravel 13. It says nothing about whether Laravel 10 or 11 support survived. Verify the require block and, ideally, run a clean install in a throwaway app matching your exact Laravel/PHP pairing. Do not infer backward compatibility from a minor version number.

Published views are a fork, not a customization layer. Once you vendor:publish the views, upstream fixes stop reaching you. Publish only the specific files you must change, or better, override via your own wrapper component and leave the vendor views untouched. If you do publish, record it in your upgrade checklist — view republishes after a package bump are the kind of thing that silently reverts a fix.

Attribute passthrough is the make-or-break detail. If the component drops $attributes, you lose required, disabled, aria-*, and your styling hook. If it merges them incorrectly, you get duplicate name attributes. Read the component's Blade file once, properly. Ten minutes there saves hours of "why isn't my class applying."

old() with multiple selects returns a string, not an array, on the first failed submit in some Laravel/Blade combinations. Guard your wrapper: (array) old($name, $selected). This is a Laravel-ism, not a package bug, but it will look like a package bug.

Clear the view cache after publishing or editing vendor views. php artisan view:clear. Blade caches compiled components and this bites every single time someone debugs a component that "isn't updating."

Accessibility is not automatic. "Accessible dropdowns" usually means sensible <label>/<select> semantics. It rarely means full ARIA compliance for a custom searchable widget — combobox roles, aria-expanded, keyboard navigation, focus return. If you have an accessibility requirement, audit the rendered output with a screen reader or axe before assuming it is met. Assume you will need to add aria-describedby for error messages yourself.

Debugging a select that renders empty. Work outward in this order: (1) is the options array actually non-empty — dump($options) in the parent view; (2) is it an associative array keyed by value, or a list of models the component does not know how to read; (3) does the component expect :options as a prop or a slot. Most "empty select" reports are an options shape mismatch, not a package defect.

Extension points worth knowing. The config file is your lever for defaults (placeholder text, CSS framework classes, AJAX route prefix, search thresholds). Set your project defaults there once rather than passing the same props 200 times. If the config lacks a key you need, that is the natural place to submit a PR — small packages respond well to focused config additions, and it is cheaper than a fork.

Version discipline for a young package. Pin the exact version. Add it to a dependency-review list. Re-test on each Laravel major. With one maintainer and one star, budget for the scenario where you own this code — the wrapper component makes that a two-hour job instead of a two-week one.

Ramp-up is discovery, not learning. The API is small; a Laravel-fluent developer will be productive in under a day. The cost is that there is no real changelog and thin release notes, so someone has to read src/ to understand config semantics and error behavior. Assign that reading to whoever owns the wrapper component, and write down what they learn — a one-page internal note is the difference between a dependency you understand and one you cargo-cult.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
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