Tricks

Render HTML in select options

Jul 12, 2022
Matthew Ost
Form builder

Sometimes a single attribute just isn't enough to identify the correct record in a select fields options. Sure, you can concatenate attributes in an accessor but it can be ugly. What if you need to display an image? Sounds like we need a little HTML!

Since Filament implemented Choices.js we've had the ability to render HTML inside each select option. You just need to switch the feature on using the new allowHtml() modifier when defining your select field.

Here's a common example of a Select field for users with avatars.

Select::make('user_id')
->label('User')
->allowHtml() // Apply the new modifier to enable HTML in the options - it's disabled by default
->searchable() // Don't forget to make it searchable otherwise there is no choices.js magic!
->getSearchResultsUsing(function (string $search) {
$users = User::where('name', 'like', "%{$search}%")->limit(50)->get();
 
return $users->mapWithKeys(function ($user) {
return [$user->getKey() => static::getCleanOptionString($user)];
})->toArray();
})
->getOptionLabelUsing(function ($value): string {
$user = User::find($value);
 
return static::getCleanOptionString($user);
})

->getSearchResultsUsing() - needs to return a key/value pair for each search result. The key should be the model (User) id and the value should be your HTML string.

->getOptionLabelUsing() - needs to return your HTML string.

To keep it DRY I've added a static method to the resource to return the view.

public static function getCleanOptionString(Model $model): string
{
return Purify::clean(
view('filament.components.select-user-result')
->with('name', $model?->name)
->with('email', $model?->email)
->with('image', $model?->image)
->render()
);
}

Note: enabling allowHtml() can introduce vulnerability to XSS attacks. Sanitize your HTML strings to stay safe!

Finally, create a blade file for the view

<div class="flex rounded-md relative">
<div class="flex">
<div class="px-2 py-3">
<div class="h-10 w-10">
<img src="{{ url('/storage/'.$image.'') }}" alt="{{ $name }}" role="img" class="h-full w-full rounded-full overflow-hidden shadow object-cover" />
</div>
</div>
 
<div class="flex flex-col justify-center pl-3 py-2">
<p class="text-sm font-bold pb-1">{{ $name }}</p>
<div class="flex flex-col items-start">
<p class="text-xs leading-5">{{ $email }}</p>
</div>
</div>
</div>
</div>
avatar

How can i use livewire component like "blade-icon" in view? if i use Purify ( https://github.com/stevebauman/purify) the package remove the component :S

Congrats, great example!

avatar
Guilherme Haynes Howe

I solved it by removing Purify

avatar
Guilherme Haynes Howe

How can I use preload with this custom component?

avatar
->options(function () {
//return options you want to preload
})
 
```