tangodev-it/filament-emoji-picker
Filament Emoji Picker adds a searchable emoji picker field to Laravel Filament forms. Users can browse categories or search, select an emoji, and save it directly to a model. It installs quickly, works with Filament admin panels and resources, and is handy for reactions, statuses, or custom fields.
Install and register
composer require tangodev-it/filament-emoji-picker
If the package ships a panel plugin (typical for Filament v3), register it in your PanelProvider:
use TangoDevIt\FilamentEmojiPicker\FilamentEmojiPickerPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugin(FilamentEmojiPickerPlugin::make());
}
Then publish anything it exposes:
php artisan vendor:publish --tag="filament-emoji-picker-config"
php artisan vendor:publish --tag="filament-emoji-picker-views"
php artisan filament:assets # if assets are registered via FilamentAsset
Where to look first
README.md — confirm the exact field/plugin class names; the package is small (26 stars) and the API surface is narrow, so the README is authoritative over any blog post.src/ — the ServiceProvider (how assets and the plugin are registered) and the field class (what it extends: Filament\Forms\Components\TextInput vs. a custom Field).resources/js/ — which JS emoji library is bundled (emoji-mart, emoji-picker-element, etc.) and how it emits the selected value back into Livewire.config/filament-emoji-picker.php — emoji set (native, apple, twitter, google), locale, and whether custom emoji are supported.First use case
A single optional "status emoji" on a model — the lowest-risk entry point:
use TangoDevIt\FilamentEmojiPicker\Forms\Components\EmojiPicker;
EmojiPicker::make('status_emoji')
->label('Status icon')
->maxLength(16);
Back it with a nullable string column and you have a working picker in under five minutes. Expand to reaction sets, category icons, or channel prefixes only after you've validated the picker renders correctly inside your panel's layout (modals and repeaters are where it breaks first).
Suffix action on a normal TextInput
If the package exposes an action rather than a full field, the least invasive integration is a suffix button so the input stays a plain TextInput (keeps your existing validation, masking, and ->reactive() behavior):
TextInput::make('emoji')
->suffixAction(
EmojiPickerAction::make('pick')
->set('emoji') // or ->statePath('data.emoji')
);
Single emoji vs. a set of reactions
For a single value, a string(16) column is enough (emoji + variation selectors + ZWJ sequences can exceed 4 bytes — do not use char(1)).
For reactions, store JSON and cast it:
protected $casts = ['reactions' => 'array'];
EmojiPicker::make('reactions')
->multiple() // verify the flag name in the README
->reactive();
Then validate against an allowlist server-side rather than trusting the client:
'reactions' => ['array', 'max:20'],
'reactions.*' => [Rule::in(AllowedEmoji::values())],
Reusable "emoji field" convention
Wrap the package field once in app/Filament/Forms/Components/EmojiField.php with your defaults (column width, max length, picker theme, placeholder). Every form then depends on your class, not the vendor namespace — this is the cheapest possible hedge against a low-traffic package changing its API or being abandoned.
Table and infolist display
Emoji render fine in TextColumn as long as the column is not truncating mid-sequence:
TextColumn::make('status_emoji')
->size(TextColumn\Size::Large)
->limit(null);
Avoid ->limit(1) — grapheme clusters (flags, family emoji, skin tones) count as multiple characters and will be visually sliced.
Inline editing and modals
Emoji pickers are popovers. Inside a Filament modal action or a repeater, the popover frequently gets clipped by an overflow-hidden ancestor. The reliable fixes, in order:
->extraModalWindowAttributes(['style' => 'overflow: visible']).wire:ignore around the picker wrapper so Livewire's DOM diffing doesn't tear down the popover mid-interaction.Livewire state flow
The JS picker emits a selection; the field writes it into Livewire state. Two rules:
#[On('emoji-selected')] rather than mutating $wire from raw JS — it keeps ->reactive() and afterStateUpdated() hooks firing.->key() / wire:key; otherwise selecting an emoji in row 2 updates row 1's picker.Testing
Do not try to drive the JS picker in tests. Set the state directly and assert persistence:
livewire(EditPost::class, ['record' => $post->id])
->fillForm(['status_emoji' => '🚀'])
->call('save')
->assertHasNoFormErrors();
$this->assertSame('🚀', $post->refresh()->status_emoji);
Add one Dusk/browser test if you genuinely need to prove the picker opens — that's the only part unit tests can't cover.
The utf8mb4 trap — this is the #1 failure mode. Emoji are 4-byte UTF-8. A legacy MySQL utf8 (3-byte) column silently truncates or throws Incorrect string value. Before anything else:
$table->string('status_emoji', 32)->nullable()->charset('utf8mb4')->collation('utf8mb4_unicode_ci');
Verify with SHOW CREATE TABLE. On MariaDB/MySQL 5.6-era schemas this often needs a migration on the whole table, not just the column.
String comparison lies. strlen('👨👩👧') is not 1 and '👍' === '👍🏽' is false. Use grapheme_strlen() (intl extension) for length validation, and normalize with Normalizer::normalize($v, Normalizer::FORM_C) before comparing against an allowlist — otherwise the same emoji submitted from different OS keyboards fails your Rule::in.
JSON escaping in APIs. Laravel's json_encode escapes emoji to \uXXXX by default unless you pass JSON_UNESCAPED_UNICODE. Response bodies look fine, but mobile clients parsing naively on older stacks can choke. Decide deliberately:
return response()->json($data, 200, [], JSON_UNESCAPED_UNICODE);
Asset registration. If you run a custom Filament theme (php artisan filament:theme), vendor CSS/JS from the plugin may not be auto-injected. Check filament:assets output and add the package's dist files to your vite.config.js input if the picker renders unstyled or not at all. Symptom: the button appears but clicking does nothing, with a 404 on a .js chunk in the network tab.
Skin tones and the picker's internal state. Some bundles remember the last-used skin tone in localStorage. In tests and demos this produces confusingly different output per browser. If the package exposes a config for it, pin it; otherwise clear storage when debugging "it picked the wrong emoji."
Bundle size. Full emoji datasets (with i18n and images) can add several hundred KB. If your panel is admin-only and infrequently used, lazy-load the picker JS on first click rather than at page load — check whether the package already does this before adding your own wrapper.
Version pinning and maintenance risk. 26 stars, effectively a single-maintainer package, MIT licensed. That's fine — but:
"tangodev-it/filament-emoji-picker": "~1.0") and read the changelog before bumping alongside a Filament major.composer.json matches yours (^3.0 vs ^4.0); a Filament 4 upgrade will likely break this before anything else in your app.Extension points worth knowing. Config for the emoji set and locale, published Blade views for the trigger button and popover markup, and (usually) a ->colors(), ->set(), or ->preview() style of fluent options. Override via published views before forking — it covers most cosmetic needs without inheriting maintenance.
Debugging checklist when the picker misbehaves:
php artisan filament:assets and hard-refresh — stale asset manifest is the most common cause of "nothing happens."How can I help you explore Laravel packages today?