Beidou
Press a key. See shortcuts. Navigate.
Keyboard navigation overlay — zero config, zero dependencies, ~2.7KB gzipped.
Features
Built entirely with vanilla JavaScript. No external libraries required, ensuring maximum stability.
Weighs in at approximately ~2.9KB gzipped. It won't bloat your application bundle.
Works seamlessly with React, Vue, Svelte, Angular, or plain HTML setups.
First-class TypeScript support with comprehensive type definitions included out of the box.
How It Works
1Step 1
Press Alt (or configurable trigger key) to show badges.
2Step 2
Press the corresponding Letter to trigger action or enter context.
3Step 3
Press Esc or trigger key again to close.
State Flow
Installation
npm install @sayagodev/beidoupnpm add @sayagodev/beidouyarn add @sayagodev/beidou<script src="https://unpkg.com/@sayagodev/beidou"></script>Initialization
import Beidou from '@sayagodev/beidou/min';
// Initialize with default options
const beidou = new Beidou();
// Or with custom options
const beidouCustom = new Beidou({
key: 'Alt',
position: 'top-right'
});HTML Attributes
Beidou uses data attributes to understand your navigation structure. Add these to your interactive elements.
| Attribute | Description | Example |
|---|---|---|
| data-ko-ctx | Defines a navigation context. Elements with this attribute will reveal child targets when activated. | <div data-ko-ctx="menu">...</div> |
| data-ko-target | Marks an element as a navigation target. Must be inside a context or the body (root context). The value is the target ID. | <button data-ko-target="save">Save</button> |
| data-ko-back | Used to navigate to the parent context. | <button data-ko-back>Back</button> |
| data-ko-skip | Instructs Beidou to ignore this element and its children. | <div data-ko-skip>...</div> |
Configuration
You can customize Beidou's behavior by passing an options object to the constructor.
const options = {
// Trigger key to toggle navigation mode
key: 'Alt',
// Key bindings configuration
keys: {
// If true, ignores elements matching CSS selectors
ignore: ['input', 'textarea', '[contenteditable]']
},
// Ring configuration
ring: {
// Color of the focus ring
color: '#4f6d8a',
// Width of the focus ring
width: '2px',
// Offset of the focus ring
offset: '2px'
},
// Badge configuration
badge: {
// Default positioning of badges
position: 'top-right', // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
// Background color
bg: '#4f6d8a',
// Text color
color: '#ffffff',
// Border radius
radius: '0px'
}
};
new Beidou(options);CSS Variables
Beidou injects CSS variables to style the focus ring and badges. You can override these in your own stylesheet.
| Variable | Default | Description |
|---|---|---|
| --ko-ring-c | #0284c7 | Ring color |
| --ko-ring-s | dashed | Ring style |
| --ko-ring-w | 2px | Ring width |
| --ko-ring-o | -2px | Ring offset |
| --ko-badge-bg | #f97316 | Badge background |
| --ko-badge-fg | white | Badge text color |
| --ko-badge-size | 11px | Font size |
| --ko-badge-w | 800 | Font weight |
| --ko-badge-font | system-ui,-apple-system,sans-serif | Font family |
| --ko-badge-p | 2px 6px | Padding |
| --ko-badge-rad | 4px | Border radius |
| --ko-badge-sh | ... | Box shadow |
| --ko-input-ring | #059669 | Input ring color |
| --ko-input-ring-s | dashed | Input ring style |
| --ko-input-bg | #059669 | Input badge background |
| --ko-input-fg | white | Input badge text color |
| --ko-input-border | none | Input badge border |
Public API
open()
Manually opens the navigation overlay.
reset()
Resets the navigation state to the root context and hides badges.
destroy()
Removes all event listeners and cleans up the DOM.
Framework Integration
Beidou works out of the box with most frameworks. Just ensure it initializes after the DOM is ready.
import { useEffect } from 'react';
import Beidou from 'beidou-nav';
export function useBeidou(options) {
useEffect(() => {
const beidou = new Beidou(options);
return () => beidou.destroy();
}, [options]);
}<!-- Vue 3 Composition API -->
<script setup>
import { onMounted, onUnmounted } from 'vue'
import Beidou from 'beidou-nav'
let beidou
onMounted(() => {
beidou = new Beidou({ key: 'Alt' })
})
onUnmounted(() => {
beidou?.destroy()
})
</script><!-- Svelte -->
<script>
import { onMount, onDestroy } from 'svelte';
import Beidou from 'beidou-nav';
let beidou;
onMount(() => {
beidou = new Beidou({ key: 'Alt' });
});
onDestroy(() => {
beidou?.destroy();
});
</script>FAQ
How do I change the activation key?
By default, Alt is used. You can change it in the constructor:
const beidou = new Beidou({ key: 'Control' });How does Beidou work?
- Press
Alt(or the configured key) to activate navigation mode - Badges (A, B, C...) will appear over each visible interactive element
- Press the corresponding letter to click on that element
- Press
Altagain, theEsckey, or click on an empty space to deactivate
Which elements support keyboard shortcuts?
Beidou automatically detects:
<a>,<button>,<input>,<textarea>,<select>- Elements with
role="button",role="link",role="tab" - Elements with
onclickortabindex >= 0
What are contexts?
data-ko-ctx and data-ko-target attributes.How do I create a sub-context?
<div data-ko-ctx="root">
<button data-ko-target="menu1">Open menu</button>
<div data-ko-ctx="menu1">
<button>Option 1</button>
<button data-ko-back>Back</button>
</div>
</div>Badges don't appear or aren't visible
Verify that:
- Elements are visible (don't have
display: noneorvisibility: hidden) - Elements have real dimensions (aren't collapsed)
- They aren't outside the viewport
- No ancestor has
overflow: hidden/clipthat clips the element
Badges appear behind other elements
The z-index of badges is 99999. If your page uses higher z-index values, you can override it with CSS:
:root { --ko-badge-z: 999999; }
.ko-badge { z-index: var(--ko-badge-z); }Which browsers does it support?
Element.closest() and CSS.escape() (ES2020+).