@katerlouis/nuxt-quicknav
v0.1.1
Published
Cmd+K jump list for Nuxt — type, arrow, enter, gone.
Maintainers
Readme
@katerlouis/nuxt-quicknav
Cmd/Ctrl+K jump list for Nuxt. Type, arrow, enter, gone.
You hand it a flat list of { label, to } and it gives you fuzzy search over
them with keyboard navigation. It has no opinion about where the list comes
from — routes, records from an API, a hardcoded array of five links.
- no UI library, no Tailwind, no build step
- fuzzy subsequence matching (
sumfindsSummer in Sweden) - arrow keys, Enter, Escape, click-outside
- z-index and colors are the host's call
- anything in your app can veto the shortcut while it's on screen
Install
npm i @katerlouis/nuxt-quicknav// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@katerlouis/nuxt-quicknav'],
});The component and the useQuickNav composable are auto-imported.
Use
Mount it once, somewhere global — app.vue is the usual spot.
<template>
<QuickNav :items="items" class="z-50" />
</template>
<script setup>
const router = useRouter();
const items = computed(() => router.getRoutes()
.map(route => route.path)
.filter(path => !path.includes(':')) // no `/album/:slug` — nowhere to go
.sort()
.map(path => ({
label: path,
to: path,
})));
</script>Press Cmd+K (Ctrl+K on Windows). Press it again while a query is typed and the query gets selected rather than the nav closing — a second Cmd+K means "no, somewhere else" without reaching for the mouse.
Loading the list lazily
@open fires every time the nav is summoned, so a list that costs a request
doesn't have to be fetched on page loads where nobody asks for it.
<QuickNav :items="items" @open="loadItems" />Props
| Prop | Type | Default | |
|-----------------|-------------------|--------------|-|
| items | { label, to }[] | required | label is searched and displayed, to is passed to NuxtLink / navigateTo |
| placeholder | string | 'Jump to…' | |
| shortcut | string | 'mod+k' | mod is meta-or-ctrl. e.g. 'alt+space', '/' |
| arrowThrottle | number | 0 | ms between arrow steps while held, against fast keyrepeat. 0 is off |
Modifiers match exactly: mod+k does not fire on mod+shift+k. Only mod is
loose, by definition. Note that e.key is the produced character, so
'shift+/' means the ? key on your layout.
Beware binding a bare letter — 'b' would fire while someone types into a form
field, because the listener is global and has no typing guard. That's fine for
mod+… combinations and a trap for everything else.
Blocking it
Some things on screen must not be interrupted — a lightbox that traps focus, a modal that owns Escape. Anything in the app can veto the shortcut:
const { block, unblock } = useQuickNav();
watch(lightboxIsOpen, isOpen => isOpen
? block('lightbox')
: unblock('lightbox'));Blockers are named rather than a single boolean: with a boolean, two blockers at once means whoever leaves first switches the nav back on while the other is still there.
Blocking guards the way in — it does not close a nav that is already open.
Styling
Colors come from custom properties, and the defaults sit in a cascade layer, so any plain rule you write beats them without a specificity contest:
.quick-nav {
--qn-bg: #111;
--qn-fg: #eee;
--qn-active-bg: #1e3a5f;
}| | |
|-|-|
| --qn-bg | panel background |
| --qn-fg | text |
| --qn-muted | placeholder, empty state |
| --qn-border | panel border |
| --qn-field-bg | input background |
| --qn-active-bg | highlighted row background |
| --qn-active-fg | highlighted row text |
| --qn-radius | panel radius |
It sets no z-index. Where it belongs in the stack depends on what else is on screen, which only the host knows — pass a class or a style and it falls through to the root element.
Requirements
Nuxt 4. @vueuse/core and @nuxt/kit come along as dependencies; both are
already in every Nuxt app, so a matching range dedupes to one copy.
License
MIT
