Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

vincent15000's avatar

How to improve this code of a custom select / options field ?

Hello,

It's the first time that I create a custom select / options field with Livewire / Blade / AlpineJS.

It works fine, but I'm sure that it can be improved.

Livewire controller

<?php

namespace App\Http\Livewire;

use Livewire\Component;

class Select extends Component
{
    public $label;
    public $nullOptionLabel;
    public $items;
    public $item_key;
    public $fieldName;
    public $class;

    public function updatedItemKey()
    {
        $this->emitUp('itemKeyUpdated', ['field' => $this->fieldName, 'key' => $this->item_key]);
    }

    public function render()
    {
        return view('livewire.select');
    }
}

Livewire view

<div>
	<input type="hidden" name="{{ $fieldName }}" wire:model="item_key">

	<x-form.select :class="$class" :label="$label" :nullOptionLabel="$nullOptionLabel" :items="$items" wire:model="item_key"></x-form.select>
</div>

Blade component

@props([
    'label',
    'nullOptionLabel',
    'items',
])

<div
    {{ $attributes->merge(['class' => 'relative flex flex-col gap-1 select-none']) }}
    x-data="{
        show: false,
        label: '{{ $label }}',
        nullOptionLabel: '{{ $nullOptionLabel }}',
        selectedItemLabel: '{{ $nullOptionLabel }}',
        items: {{ json_encode($items) }},
        item_key: @entangle($attributes->wire('model')),
        selectItem(itemKey) {
            this.item_key = itemKey;
            this.show = false;
        }
    }"
    x-init="
        if (item_key) {
            const selectedItem = items.find(function (item) {
                return item.key == item_key;
            });
            selectedItemLabel = selectedItem.value;
        } else {
            selectedItemLabel = nullOptionLabel;
        }
        $watch('item_key', function (value) {
            if (value) {
                const selectedItem = items.find(function (item) {
                    return item.key == value;
                });
                selectedItemLabel = selectedItem.value;
            } else {
                selectedItemLabel = nullOptionLabel;
            }
        });
    "
    @click.away="show=false"
>
    <label class="text-gray-500" for="item_key" x-html="label"></label>

    <div class="flex justify-between items-center px-3 py-1 border border-black rounded cursor-pointer" @click="show = !show">
        <label class="cursor-pointer" x-html="selectedItemLabel"></label>

        <span class="text-xs"><i class="fa-solid fa-chevron-down"></i></span>
    </div>

    <template x-if="true">
        <ul class="z-20 absolute top-16 left-0 w-full max-h-60 overflow-y-auto bg-slate-800 border-2 border-slate-700 text-slate-300 rounded cursor-pointer" x-show="show" x-transition>
            <li class="w-full px-3 py-1 first:rounded-t last:rounded-b hover:bg-lime-500 hover:text-black" :class="item_key == null ? 'text-lime-500' : ''" @click="selectItem(null)">{{ $nullOptionLabel }}</li>

            @foreach($items as $item)
                <li class="w-full px-3 py-1 first:rounded-t last:rounded-b hover:bg-lime-500 hover:text-black" :class="item_key == {{ $item['key'] }} ? 'text-lime-500' : ''" @click="selectItem({{ $item['key'] }})">{{ $item['value'] }}</li>
            @endforeach
        </ul>
    </template>
</div>

And here how I use it in a view.

@livewire('select', [
	'label' => 'Compte',
	'nullOptionLabel' => 'Choisir un compte',
	'items' => $accounts,
	'item_key' => $transaction->account_id,
	'fieldName' => 'account_id',
])

Thanks for your suggestions.

V

0 likes
5 replies
Snapey's avatar

What does it do that a native html select cannot?

1 like
vincent15000's avatar

@Snapey Perhaps I'm wrong, but I think that it's not possible to customize the design of the options. For example if I need to add an image or other datas, each option could be presented with an avatar on the left and a text on the right. In such situations I think that it's necessary to write entirely the component, or not ?

So for me it's a kind of training ;).

Please or to participate in this conversation.