Description: AlpineJS allows you to easily listen for specific key presses (e.g., .enter, .escape) or key combinations (e.g., .ctrl.enter) on input elements or globally on the window or document. This is achieved by adding modifiers to standard DOM event listeners like @keydown or @keyup.
Keyboard event listeners in AlpineJS use the x-on: directive or its shorthand @. For example, @keydown, @keyup.
@keydown.enter="doSomething": This directive listens for the keydown event on the element. The .enter modifier filters these events, so the doSomething method is only called if the key pressed down is the "Enter" key.
@keyup.escape="doSomethingElse": This listens for the keyup event. The .escape modifier ensures doSomethingElse is called only when the "Escape" key is released.
.enter, .escape, .space, .tab.arrow-up, .arrow-down, .arrow-left, .arrow-right.home, .end, .page-up, .page-down.delete (for Forward Delete), .backspaceFor alphabetic keys, use their lowercase character (e.g., @keydown.a for the 'a' key). For numeric keys on the main keyboard, use the digit (e.g., @keydown.1 for the '1' key). For other keys, Alpine generally uses kebab-cased versions of the event.key values (e.g., PageDown becomes .page-down).
.ctrl: An alias for .control. Simplifies writing listeners for the Control key..cmd: An alias for .meta specifically intended for targeting the Command key on macOS. On Windows/Linux, this will listen for the Meta key (often the Windows key or Super key). Using @keydown.cmd.s alongside @keydown.ctrl.s can provide cross-platform save shortcuts..shift: Checks for the Shift key..alt: Checks for the Alt key (Option key on macOS)..meta: Checks for the Meta key (Command key on macOS, Windows key on Windows/Linux)..control: Checks for the Control key.@keydown.ctrl.enter="save" calls save only when Enter is pressed while Ctrl is also held down.@keydown.shift.alt.arrow-up="moveItemFast"..ctrl.shift vs .shift.ctrl) does not typically matter..prevent: Automatically calls event.preventDefault() on the triggered DOM event. Useful for stopping default browser actions (e.g., stopping a form from submitting on Enter if you want to handle it with JavaScript). Example: @keydown.space.prevent="handleSpacebar"..stop: Automatically calls event.stopPropagation(), preventing the event from bubbling up the DOM tree..exact: When used with system modifiers (.ctrl, .shift, .alt, .meta), this ensures the handler fires only if those exact modifiers and no others are pressed. For instance, @keydown.ctrl.a.exact="selectOnlyWithCtrl" fires for Ctrl+A, but NOT for Ctrl+Shift+A. Without .exact, @keydown.ctrl.a would fire for both..self: The handler is dispatched only if event.target is the element itself, not a child element. (More common for click events)..window: Attaches the event listener to the global window object instead of the element it's declared on. Example: @keyup.window.escape="closeModal"..document: Attaches the event listener to the global document object. Similar to .window.keyup vs keydown inappropriately:
keydown fires when the key is initially pressed down. If the key is held, it might fire repeatedly (key repeat). This is often better for actions that should feel immediate, like form submission on "Enter" or arrow key navigation.
keyup fires when the key is released. This is suitable for actions that should occur after typing is complete (e.g., input validation) or to avoid repeated actions if a key is held. For instance, if you want to count distinct key presses, keyup might be more reliable than keydown with key repeat.
Consider the user experience: for "submit on Enter," keydown usually feels more responsive.
.window or .document) firing too often or when not intended:
When you attach a listener like @keydown.window.escape="closeModal", the closeModal function is called every time the Escape key is pressed, regardless of which part of your application is active or visible.
Pitfall: If multiple components use similar global listeners (e.g., several different modals all listen for Escape), they might all react. A global listener might also act even when its component isn't in a state where it should (e.g., a modal's Escape listener firing even if the modal is already closed).
Solution: Always include conditional logic within your handler methods. For example, if closeModal() is triggered by @keydown.window.escape, it should first check something like if (this.isModalOpen) before changing state. This ensures only the relevant, active component acts.
Many key combinations are reserved by browsers (e.g., Ctrl+S for Save, Cmd+R or F5 for Refresh, Ctrl+T for New Tab) or the operating system.
While AlpineJS allows .prevent (e.g., @keydown.ctrl.s.prevent="customSave") to try and stop the default browser action, this is not always effective for all shortcuts across all browsers/OSes, and can lead to a confusing user experience if an expected behavior is overridden.
Best Practice: It's generally better to avoid overriding deeply ingrained, universally expected browser or OS shortcuts. If you must, ensure the user is clearly informed. For application-specific actions, choose less common key combinations. Test thoroughly on different browsers and operating systems if you attempt to override common shortcuts.
The @keydown.window.escape listener is attached to the main component. It will call closeModalIfOpen().
(Using @keydown.ctrl.enter.prevent and @keydown.meta.enter.prevent. .meta is for Command key on Mac.)
Focus the input below and try these exact combinations:
@keydown.a="handler"@keydown.arrow-up.prevent="handler" (prevents cursor move)@keydown.shift.s.exact="handler"@keydown.alt.p.prevent="handler" (may prevent typing 'p' or trigger OS/browser menu)