Beidou

北斗

Press a key. See shortcuts. Navigate.

Keyboard navigation overlay — zero config, zero dependencies, ~2.7KB gzipped.

Features

Zero Dependencies

Built entirely with vanilla JavaScript. No external libraries required, ensuring maximum stability.

Tiny Footprint

Weighs in at approximately ~2.9KB gzipped. It won't bloat your application bundle.

Framework Agnostico

Works seamlessly with React, Vue, Svelte, Angular, or plain HTML setups.

Full TypeScript

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

Inactive
Press Alt
Active: Root
Press Context Key
Active: Sub

Installation

npm install @sayagodev/beidou
pnpm add @sayagodev/beidou
yarn 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.

AttributeDescriptionExample
data-ko-ctxDefines a navigation context. Elements with this attribute will reveal child targets when activated.<div data-ko-ctx="menu">...</div>
data-ko-targetMarks 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-backUsed to navigate to the parent context.<button data-ko-back>Back</button>
data-ko-skipInstructs 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.

VariableDefaultDescription
--ko-ring-c#0284c7Ring color
--ko-ring-sdashedRing style
--ko-ring-w2pxRing width
--ko-ring-o-2pxRing offset
--ko-badge-bg#f97316Badge background
--ko-badge-fgwhiteBadge text color
--ko-badge-size11pxFont size
--ko-badge-w800Font weight
--ko-badge-fontsystem-ui,-apple-system,sans-serifFont family
--ko-badge-p2px 6pxPadding
--ko-badge-rad4pxBorder radius
--ko-badge-sh...Box shadow
--ko-input-ring#059669Input ring color
--ko-input-ring-sdashedInput ring style
--ko-input-bg#059669Input badge background
--ko-input-fgwhiteInput badge text color
--ko-input-bordernoneInput 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>

Nested Menus

Create complex hierarchies using contexts.

 {/* body with data-ko-ctx="root" exist */}
  <button data-ko-target="settings">⚙️ Settings</button>
  <div data-ko-ctx="settings">
    <button data-ko-target="profile">Profile</button>
    <button data-ko-target="security">Security</button>
    <button data-ko-back><- Back</button>
  </div>
  <div data-ko-ctx="profile">
    <button onClick={edit()}>Edit</button>
    <button data-ko-back><- Back</button>
  </div>
  <div data-ko-ctx="security">
    <button onClick={changePw()}>Change Password</button>
    <button data-ko-back><- Back</button>
  </div>

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?
  1. Press Alt (or the configured key) to activate navigation mode
  2. Badges (A, B, C...) will appear over each visible interactive element
  3. Press the corresponding letter to click on that element
  4. Press Alt again, the Esc key, 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 onclick or tabindex >= 0
What are contexts?
Contexts allow you to navigate between different "levels" of the UI (such as nested menus or modals). They use the 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>
data-ko-ctx="menu1"
Badges don't appear or aren't visible

Verify that:

  1. Elements are visible (don't have display: none or visibility: hidden)
  2. Elements have real dimensions (aren't collapsed)
  3. They aren't outside the viewport
  4. No ancestor has overflow: hidden/clip that 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?
All modern browsers: Chrome, Firefox, Safari, Edge. Requires Element.closest() and CSS.escape() (ES2020+).
Does it work on mobile?
Beidou is designed for keyboard navigation. On mobile devices without a physical keyboard, it has no practical use.
Is it accessible (a11y)?
Beidou improves keyboard navigation, but it does not replace proper semantic markup. Use it as an additional layer, not as the sole means of navigation.