Custom Select

{note} You're browsing the documentation for an old version of laravel-form-components. Consider upgrading your project to v8. Check your version with the following command:

composer show rawilk/laravel-form-components

Heavily based on Tailwind UI's custom selects, the custom-select component provides a nice alternative to the native select menu. Using this menu will provide your select menus with search functionalities and custom option markup while still providing usability functionalities such as keyboard navigation.

The custom select component requires Alpine.js and Popper.js, as well as some custom JavaScript written into the package to work. Ensure you have the proper directives in your layout file. In production, we recommend you install and compile the JavaScript libraries before you deploy:

{tip} See the JavaScript Dependencies section for more information on installing them.

You can use the select menu by providing some basic options.

<x-custom-select :options="['foo', 'bar']" />

This will output a custom select menu with options for foo and bar. The menu is toggled open and closed by using a <button> element, and renders each option in an <li> tag inside of a <ul>. We have done our best to include all of the necessary aria attributes for accessiblity, but we are by no means accessibility experts. If you notice any accessibility issues, please submit a PR to the github repository to fix them.

The custom select component accepts either an array or collection via the options attribute. If you provide an array of strings, the component will use the strings as both the key and values of the options. For most cases however, you should provide an array of keyed arrays for each option, or you can even pass in an array of Eloquent models as options.

<x-custom-select :options="[
    ['value' => 'foo', 'text' => 'Foo'],
    ['value' => 'bar', 'text' => 'Bar'],
]" />

<!-- using models -->
<x-custom-select :options="[\App\Models\User::get(['id', 'name'])]" value-field="id" text-field="name" />

By default, the component will look for a value key for the option value, and a text key for the option text. When you are using Eloquent models for options, most of the time this won't work. For this reason, you can specify which keys the option should use for the value and text of the option via value-field and text-field.

By using the optionDisplay slot, you can customize how each option is rendered.

<x-custom-select :options="['foo', 'bar']">
    <x-slot name="optionDisplay">
        <div class="flex items-center space-x-2">
            <span class="block bg-green-500 rounded-full w-4 h-4"></span>
            <span x-html="option.text"></span>            

In this example, we are prepending a green dot in front of the option text for each option. As you can see, any kind of markup is allowed here, so you are able to add whatever kind of html you need for each option.

Since each option is rendered in an x-for loop via alpine, you will need to use directives such as x-text or x-html to render an option's text. In the optionDisplay slot, you will have access to following JavaScript properties:

  • index: The index of the current option
  • option: The JavaScript representation of the current option.

Note: Each option object will have whatever properties you included on the object when you passed them into the options attribute, however they will always have the value, text, and disabled attributes on each option object. If you have any of those keys on your object, they will be overwritten when the component parses your options.

New in v2, you can customize the button text when there is a selected option via the buttonDisplay slot. By default, the text of an option is used as a display in the button, but you can customize this to match how your options are displayed in the dropdown list.

<x-custom-select :options="['foo', 'bar']">
    <x-slot name="buttonDisplay">
        <div class="space-x-2 flex items-center">
            <span class="block w-5 h-5 rounded-full bg-green-500"></span>
            <span x-text="optionDisplay(value)"></span>

Since value is the actual value of the selected option, you will need to find the option first and then render the option's text. This has been made easier by using the component's optionDisplay() helper method, which takes the value of the option you're trying to find in as a parameter. If it finds an option, it will return the text of the found option. If you need a different attribute, you can find the option yourself by:

<span x-text="data.find(option => option.value === value).someOtherField"></span>

It is recommended to use data instead of options to find the selected option, since options will get mutated if you have a filter in place.

You may disable specific options by setting disabled to true on the option:

<x-custom-select :options="[['value' => 'foo', 'text' => 'Foo', 'disabled' => true]]" />

You can use a different key for disabled on the option, by specifying it via the disabled-field attribute on the select.

<x-custom-select :options="[['value' => 'foo', 'text' => 'Foo', 'inactive' => true]]" disabled-field="inactive" />

You can specify an option as an "optgroup" header by using a "label" key on the option object:

<x-custom-select :options="[
    ['label' => 'Opt Group', 'options' => ['foo', 'bar']]

In this example, an opt group with the label "Opt Group" will be rendered, as well as the options underneath for "foo" and "bar". You must include the label and options keys on the optgroup option object.

You may provide search functionality on a custom select by setting filterable on the component to true.

<x-custom-select :options="$options" filterable />

This will provide basic search functionality, which will hide any non-matching options as the user types.

If you use Livewire, you can easily add server-side filtering of options via a custom wire:filter attribute on the component.

<x-custom-select :options="$options" filterable wire:filter="searchMethod" />

In this example, this will require you to have a method called searchMethod on your livewire component. Our JavaScript will call that method via livewire's JavaScript API and expects an array or collection of your filtered options to be returned. The component debounces the search input by 300ms so each keystroke is not triggering another ajax request to your server.

You can easily make a select accept multiple selected options by using the multiple attribute.

<x-custom-select :options="$options" multiple />

If you are customizing the selected text via the buttonDisplay slot, you should use optionDisplay(value[0]) if you are referencing the option text like that, since value will be an array.

With multiple selects, you may limit the number of options a user may select with the max attribute.

<x-custom-select :options="$options" multiple max="3" />

In this example, the user will only be able to select a maximum of 3 options.

For times that you want to allow users to clear the select option(s), you can mark a custom select as optional. This works for both single and multi-select modes, and provides a clear button next to the selected option text.

<x-custom-select :options="$options" optional />

If you have a custom select whose options depend on the selection of another select, or just some kind of condition to be met, you can livewire events to update the options in the select.

In your component definition, pass in an array of livewire event names the select should listen for:

<x-custom-select :options="$options" :wire-listeners="['some-event-name']" />

Now in your livewire component, you just need to emit the event(s) you are passing in to the select whenever the options need to be updated:


namespace App\Http\Livewire;

use Livewire\Component;

class MyComponent extends Component
    public function someMethodToBeTriggered()
        // Retrieve your options based on your component state
        $options = [
            ['value' => 'foo', 'text' => 'bar'],

        // Emit your event, passing in your options as the payload
        $this->emit('some-event-name', $options);

Our JavaScript event listener is expecting an array of options as the payload from the event, so be sure to pass the event emitter your options.

As of v4, the custom-select component makes use of Popper.js for positioning the select menu. This should remove the need for fixed positioning the select menu now. In addition to positioning the menu when opened, Popper.js will also re-position the menu as needed when the page is scrolled.

The custom select component supports leading addons, but since there are already elements appended to the end of the button trigger, trailing addons are not supported. For more information on addons, see the input documentation.

Caught a mistake? Suggest an edit on GitHub