What it was
In the last update of Stimulus v3.2.2, the ability to filter the event by PageDown and PageUp keys was added.
Before that, in v3.2.1, it looked like this:
<div data-action="keyup->myController#myfunction"></div>
and some like this…
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
connect() {
// Add a keydown event listener when the controller is connected to the DOM
document.addEventListener("keydown", this.handleKeyDown);
}
disconnect() {
// Remove the keydown event listener when the controller is disconnected
document.removeEventListener("keydown", this.handleKeyDown);
}
handleKeyDown(event) {
// Access the pressed key from the event
const pressedKey = event.key;
// Do something with the pressed key
console.log(`Pressed key: ${pressedKey}`);
}
}
What the new
For now, you can call page_up/page_down in action, like:
<div data-action="keydown.page_up->myController#myfunction"></div>
Keyboard filters themselves arrived in Stimulus 3.2.0, with keys such as enter, esc and the arrows. Version 3.2.2 added page_up and page_down to that list (release notes). For these two keys you no longer have to add and remove event listeners in the controller. You name the key and the event type directly in the HTML, in the data-action attribute.
For example, in the updated version, the element is set to trigger the myfunction method in myController specifically on a keydown event for the Page Up key. This makes the code cleaner and more readable, emphasizing the intent directly in the HTML markup.
Why your Page Up handler might not fire #
The element never gets the key. A keydown event starts at the focused element and bubbles up through its parents. A plain <div> can’t take focus, so unless focus is on something inside it, the action above never runs. With Stimulus 3.2.2 in Chrome, an empty <div> without tabindex logged nothing, while the same <div> with tabindex="0" fired once it had focus.
You meant the whole page. The old controller listened on document, so it caught Page Up wherever focus was. The declarative version of that is the @window (or @document) suffix from the Stimulus actions reference:
<div data-controller="pager"
data-action="keydown.page_up@window->pager#previous:prevent
keydown.page_down@window->pager#next:prevent">
</div>
:prevent calls event.preventDefault() before your method runs, so the browser doesn’t also scroll the page. Both methods received the event with defaultPrevented already true, and Arrow Down called neither.
You are on an older Stimulus. On 3.2.1 the same page never ran the method, and every key press threw contains unknown key filter: page_up in the console. If you see that error, check the @hotwired/stimulus version in your package.json or importmap. You need 3.2.2 or later.
If you need a key that isn’t in the built-in list, the same reference page shows how to add your own keyMappings with a custom schema.



