Using escape key to close a modal

Anyone have any tips to close a modal from the escape key?

I have a dark backdrop with a close button in the top corner (works)

I have a modal window with a close button (works)

I’m inserting the modal into the page only when it needs to show.

I want the escape key to function anytime the modal is loaded. All the examples seem to assume you are attaching the event listener to an input field?

You can add an event listener on your body for keydown and filter for escape and have that action trigger your close modal function. With alpine, it’s pretty simple.

<body x-on:keydown.escape="modal = false">

Here, I’m using modal as a boolean to x-show my modal but you can have it run a function that does your hide/show modal logic and also filter for different usage based on the scenario - in your case, you can have it only run when your modal is active.

If you’re using vanillajs, the procedure is the same: add keydown listener and execute logic.

1 Like

Presumably x-data on the body also? Isn’t that putting a lot in scope for alpine?

The body is a long way from the modal. I have a page which extends a base layout which contains multiple livewire components, one of which has the modal open.

@shortbrownman

Depends on what you consider a lot. A handful of booleans watching things doesn’t feel heavy to me. x-data would be on body in my example. You’re going to have to monitor for window keydown if you want the modal to feel like a modal. You can have your modal variables confined to the modal itself but the keydown wouldn’t be activated in that scope unless you called a focus to it.

What I mean is, the layout file should not have things in it to control grandchild components, and thats where the body tag is.

Alpine doesn’t share scope so if I have the need to control something globally, it has to be scoped at the top. If this feels messy or won’t work for your needs, you can scope it closer to your component but you’ll have to tinker a bit to get it where you want it. It’s on my body because there are several things that need to call the modal.

@shortbrownman thanks for the continued input. I think one of my issues is that I don’t think the javascript is reevaluated when inserting the modal. Putting it on the body means its there on the page from the start.

I got this working with your help;

<body x-data x-on:keydown.escape="window.livewire.emit('closeModals')">

So if there are any livewire facilitated modals on screen, they can listen for this global event and set their modal to close. I had originally just ‘close’ but this was conflicting with some native event or method.

I might try moving this next. I don’t like it being on the body as I will forget its there and then have some strange side effects I’m sure.

Ah, you’re toggling visibility within livewire. Just a head’s up, doing it this way will be a little more laggy once on a server outside of your local network. This is because you’re transmitting the close action through ajax and then it has to round trip back to execute it on the front end.

In this case, you wouldn’t need the x-data as it’s handled directly in livewire. But if you run into a sluggish interface after development, handle the modal closing in javascript. You can cascade the window.livewire.emit to handle whatever backend stuff you need to handle for cleaning up the closed modal.

If you want to keep it within your component, you can use a blade stack for your javascript:

// modal.blade.php

@push('stack')
<script>
document.onkeydown = function(e) {
    if (e.keyCode == 27) {
        window.livewire.emit('closeModals')
    }
};
</script>
@endpush

Hi @Snapey

why not binding the keydown event listener to the window? You can do this from the alpine component itself without polluting anything outside.
See my real example below where the modal is closed when Escape or Enter keys are pressed.

As a bonus, it also shows how the popup/modal becomes visible whenever a “new-message” event is dispatched from anywhere in the page. (Notice the ‘.window’ and ‘.document’)

<div x-data="{ open: false, message: null }"
     x-show="open"
     x-on:new-message.document="message = $event.detail.newMessage; open = true"

     x-on:keydown.escape.window="open = false"
     x-on:keydown.enter.window="open = false"

     class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center z-50"
     style="display:none;"
> 
    <span x-text="message && message.title"></span>
</div>

Hope it helps.
Cheers

edit: relevant docs here: https://github.com/alpinejs/alpine#x-on

2 Likes

Thanks! That is definitively the solution, so we can attach the event to windows, but the code can still directly with the component.