Closing a Filament dropdown with Alpine

Overview

This article details an approach to closing a Filament's dropdown (Blade component) programmatically using Alpine.js. We'll create a custom Alpine.js magic function and call that function in the @click handler of individual dropdown items. Our magic function will be defined in a dedicated JavaScript file and included via Filament's asset registration.

Use case

When using Filament's dropdown Blade component, you may want to close the dropdown panel upon an item being clicked.

Examples of when you'd want to do this include:

  • utilizing the dropdown component in a custom Filament field
  • creating a custom action menu using the dropdown component

Assumptions

You have a working Laravel project with:

  • Filament 4 installed and configured
  • a Blade template containing a Filament dropdown component you want to close programmatically using JavaScript

Creating the Alpine magic function

Create the following JavaScript file at a resource path that makes sense to you (e.g. resources/js/functions/close-dropdown.js).

📁
resources/js/functions/close-dropdown.js
1document.addEventListener('alpine:init', () => {
2 Alpine.magic('closeDropdown', (el) => () => {
3 const dropdown = el.closest('[x-data*="filamentDropdown"]');
4 if (dropdown) Alpine.$data(dropdown).close();
5 });
6});

In the above JavaScript snippet, we're using the alpine:init lifecycle hook, which fires:

after Alpine is loaded, but BEFORE it initializes itself on the page

Within the hook's callback, we register a new "magic" function named closeDropdown. The callback returns a function to be invoked whenever $closeDropdown() is called within an Alpine.js component.

The first argument to the callback, el, is the DOM element the magic function was triggered from. Using this DOM element, we find the closet DOM element that matches the query selector [x-data*="filamentDropdown"]. filamentDropdown is the name of the Alpine data provider Filament uses internally for the dropdown component.

Then we use the Alpine magic function $data, scoped to the Filament dropdown DOM element, to call the close function on the dropdown's Alpine data instance. The close function we're calling is the same function Filament's internals use to close the dropdown in Filament Action menus.

Registering the script as a Filament asset

Now that we have a custom Alpine magic function in a dedicated JavaScript file, we need to register the asset using the mechanism provided by Filament. This will cause our JavaScript file to be included wherever the @filamentScripts Blade directive is included.

Add the following in the boot method of a service provider:

📁
app/Providers/AppServiceProvider.php
1<?php
2 
3namespace App\Providers;
4 
5use Filament\Support\Assets\Js;
6use Filament\Support\Facades\FilamentAsset;
7use Illuminate\Support\ServiceProvider;
8 
9class AppServiceProvider extends ServiceProvider
10{
11 public function boot(): void
12 {
13 FilamentAsset::register([
14 Js::make('close-dropdown', __DIR__.'/../../resources/js/functions/close-dropdown.js'),
15 ]);
16 }
17}

Publishing Filament assets

Don't forget to run the following artisan command to publish your updated assets to the /public directory:

1php artisan filament:assets

Using the magic function

To use our new Alpine magic function, we simply need to call $closeDropdown() within the @click handler of a Filament dropdown list item. Which items make sense to close the dropdown on click depends on the use cases of your application.

1<x-filament::dropdown>
2 <x-slot name="trigger">
3 <x-filament::button>
4 Dropdown trigger
5 </x-filament::button>
6 </x-slot>
7 
8 <x-filament::dropdown.list>
9 <x-filament::dropdown.list.item @click="$closeDropdown()">
10 Clicking this item will close the dropdown
11 </x-filament::dropdown.list.item>
12 <x-filament::dropdown.list.item>
13 Another item which does not close the dropdown
14 </x-filament::dropdown.list.item>
15 </x-filament::dropdown.list>
16</x-filament::dropdown>
Using our magic function in tandem with Livewire's $wire object to call methods on your Livewire component is quite powerful!

Conclusion

The impetus for this article was my surprise in not finding a straightforward way to accomplish the goal of programmatically closing a Filament dropdown when using the provided Blade component.

If anyone knows of a less involved approach than creating a custom Alpine.js magic function that directly accesses Alpine's data through $data, I'd love to hear about it!

🤖
Did you spot a mistake in this article? Have a suggestion for how something can be improved? Even if you'd just like to comment or chat about something else, I'd love to hear from you! Contact me.

Syntax highlighting by Torchlight.dev

End of article