Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Emoji Picker Laravel Package

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.

Deep Wiki
Context7

Getting Started

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

  1. 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.
  2. 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).
  3. resources/js/ — which JS emoji library is bundled (emoji-mart, emoji-picker-element, etc.) and how it emits the selected value back into Livewire.
  4. 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).

Implementation Patterns

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:

  1. Mount the picker with a teleport/portal option if the package supports it.
  2. Give the modal body ->extraModalWindowAttributes(['style' => 'overflow: visible']).
  3. Use 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:

  • If you're building a custom wrapper, dispatch a browser event and listen with #[On('emoji-selected')] rather than mutating $wire from raw JS — it keeps ->reactive() and afterStateUpdated() hooks firing.
  • In repeaters, always pass a stable ->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.

Gotchas and Tips

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:

  • Pin to a minor version ("tangodev-it/filament-emoji-picker": "~1.0") and read the changelog before bumping alongside a Filament major.
  • Confirm the Filament version constraint in its composer.json matches yours (^3.0 vs ^4.0); a Filament 4 upgrade will likely break this before anything else in your app.
  • MIT means you can fork. If you rely on it heavily, vendor the field class into your own namespace as described above — it's the difference between a 10-minute fix and a blocked release.

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:

  1. php artisan filament:assets and hard-refresh — stale asset manifest is the most common cause of "nothing happens."
  2. Browser console for Alpine errors; a duplicate Alpine instance (from another plugin) kills the popover silently.
  3. Inspect the Livewire payload after selection — if the value is there but the DB is empty, it's the utf8mb4 problem, not the picker.
  4. Test the same field on a bare page outside modals/repeaters to isolate overflow/clipping issues.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle