search
Search modal with Pagefind integration and keyboard shortcuts.
Press Cmd+K (Mac) or Ctrl+K (Windows/Linux) right now. The search modal that opens is powered by this controller.
Usage
<div data-controller="search">
<!-- Trigger Button -->
<button data-action="click->search#open">
Search
</button>
<!-- Modal -->
<div data-search-target="modal"
data-action="click->search#closeOnBackdrop keydown.escape@window->search#close"
class="hidden fixed inset-0 bg-gray-900/50 z-50">
<div class="container mx-auto px-4 py-16 max-w-2xl">
<div class="bg-white rounded-lg shadow-xl">
<div class="flex justify-between items-center p-4 border-b">
<h2>Search</h2>
<button data-action="click->search#close">Close</button>
</div>
<div data-search-target="container" class="p-4"></div>
</div>
</div>
</div>
</div>
Targets
| Target | Purpose |
|---|---|
modal |
The modal overlay |
container |
Container for Pagefind UI |
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
| Cmd+K / Ctrl+K | Open search |
| Escape | Close search |
Pagefind Setup
Add Pagefind to your build script:
{
"scripts": {
"build": "NODE_ENV=production eleventy && npx pagefind --site dist"
}
}
Include Pagefind assets in your scripts partial:
<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js"></script>
Source
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["modal", "container"];
connect() {
this.isOpen = false;
this.boundHandleKeydown = this.handleKeydown.bind(this);
document.addEventListener("keydown", this.boundHandleKeydown);
this.initializePagefind();
}
disconnect() {
document.removeEventListener("keydown", this.boundHandleKeydown);
}
initializePagefind() {
if (typeof PagefindUI !== "undefined" && this.hasContainerTarget) {
new PagefindUI({
element: this.containerTarget,
showSubResults: true,
showImages: false,
});
}
}
open() {
this.isOpen = true;
this.modalTarget.classList.remove("hidden");
setTimeout(() => {
const input = this.modalTarget.querySelector("input");
if (input) input.focus();
}, 100);
}
close() {
this.isOpen = false;
this.modalTarget.classList.add("hidden");
}
handleKeydown(event) {
if ((event.ctrlKey || event.metaKey) && event.key === "k") {
event.preventDefault();
this.open();
}
}
closeOnBackdrop(event) {
if (event.target === this.modalTarget) this.close();
}
}