Skip to content

Select

The select lets the user pick from a list of options. Create one with Select::make() and pass the options with ->options(). Each option is a label/value pair built with Select::option().

Select::make('country', 'Country')
->placeholder('Pick a country')
->options([
Select::option('Germany', 'de'),
Select::option('France', 'fr'),
Select::option('Spain', 'es'),
Select::option('Italy', 'it'),
])

->placeholder() sets the text shown before a value is chosen. It is not selectable as a value. ->emptyLabel() sets the message shown in the dropdown when no options match (default “No options”), and ->searchPlaceholder() sets the search box placeholder (default “Search…”).

->enum() builds the options from a backed enum. Pass the enum class for every case, or an array of cases for a subset. Labels come from the enum’s HasLabel contract when it implements one, otherwise the case name is humanized.

Select::make('status', 'Status')->enum(OrderStatus::class);

->multiple() lets the user choose more than one option. The field submits an array of values.

Select::make('languages', 'Languages')
->multiple()
->placeholder('Choose languages')
->options([
Select::option('PHP', 'php'),
Select::option('JavaScript', 'js'),
Select::option('Go', 'go'),
Select::option('Rust', 'rust'),
])

A static ->options() select has no search box — the dropdown shows the full list. Call ->searchable() to add a search box and resolve options on the server (->searchPlaceholder() then sets its placeholder). For a static list long enough to need filtering, prefer ->searchable().

->searchable() turns the select into a server-driven search. The resolver receives $search (the query string) and returns the matching options; it may also inject $get/$state/$component and any container type such as Request through closure evaluation. The option value is the entity identifier and is fully controlled by the resolver, so it scales to large datasets without shipping every option to the client.

Select::make('author_id', 'Author')
->searchable(fn ($search) => User::query()
->where('name', 'like', "%{$search}%")
->limit(10)
->get()
->map(fn (User $user) => Select::option($user->name, (string) $user->id))
->all());

On an edit form the stored value needs a label to display. ->resolveSelectedUsing() receives the current value(s) — always as an array — and returns their options:

Select::make('author_id', 'Author')
->searchable(/* … */)
->resolveSelectedUsing(fn (array $values) => User::query()
->whereIn('id', $values)
->get()
->map(fn (User $user) => Select::option($user->name, (string) $user->id))
->all());

Backing a select with an Eloquent model is the common case, so ->optionsFrom() takes an OptionSource and wires both the search and the selected-label resolution from one declaration. EloquentOptions is the shipped source:

use Lattice\Lattice\Core\EloquentOptions;
Select::make('author_id', 'Author')
->optionsFrom(EloquentOptions::make(User::class)->label('name'));

->label() and ->value() pick the display and value columns (value defaults to the key). Add ->searchColumns(['name', 'email']) to match more than the label, ->scope(fn ($query) => $query->where('active', true)) to constrain the query, and ->limit(50) to cap results.

optionsFrom only resolves the options. The selected value (e.g. author_id) is a normal field value, so it is validated and returned in the submitted array like any other — your handler persists it. Lattice forms stay decoupled from the model.

Select shares label, default value, required, disabled, read-only, and visibility options with every field — see Fields. For validation and conditional behavior, see Validation and Conditional fields.