@stackra/kbd
v2.0.0
Published
Command palette, keyboard shortcut registry, recents + favorites, sub-page navigation, confirm dialogs, walkthrough, draggable palette, and palette theme switcher for Stackra apps. Composed on HeroUI Pro Command + TanStack Hotkeys with a DI-driven archite
Maintainers
Readme
@stackra/kbd
Command palette, keyboard shortcut registry, recents + favorites, sub-page
navigation, confirm dialogs, walkthrough, draggable palette, and palette theme
switcher for Stackra apps. Composed on HeroUI Pro Command + TanStack Hotkeys
with a DI-driven architecture that scales past the single-file command-kit
reference.
Status: alpha. Fresh rewrite in progress — see
.ref/kbd-legacy/README.mdfor the archived previous version.
Subpath layout
| Entry | Ships |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| @stackra/kbd | KbdModule (mounted at the app root), services, registries, stores, tokens, pure utilities, TanStack Hotkeys re-exports. |
| @stackra/kbd/react | WebKbdModule, WebKeyboardListener, <KbdProvider>, palette + walkthrough + theme switcher components, every React hook, compound Kbd API. |
| @stackra/kbd/native | NativeKbdModule, NativeKeyboardListener, <PaletteBottomSheet>, <WalkthroughCoachmark>, mobile-scoped hooks. |
| @stackra/kbd/testing | <TestKbdProvider>, createMockCommand(), mockCommandService. |
Quick start (once the build lands)
// app.module.ts
import { Module } from "@stackra/container";
import { WebKbdModule } from "@stackra/kbd/react";
@Module({
imports: [
WebKbdModule.forRoot({
toggleShortcut: "mod+k",
defaultPaletteTheme: "raycast",
categoryOrder: ["Navigation", "Actions", "Preferences", "Help"],
}),
],
})
export class AppModule {}WebKbdModule composes the cross-platform KbdModule and adds
WebKeyboardListener — the browser window.addEventListener("keydown", …)
bridge that forwards each event into the shared ShortcutService. Wire this
module INSTEAD of KbdModule on the web; the native subpath ships its own
NativeKbdModule that adds the RN keyboard-event seam.
// somewhere in the tree
import { useRegisterCommands } from "@stackra/kbd/react";
import { HomeIcon } from "@heroicons/react/24/outline";
useRegisterCommands([
{
id: "nav.home",
label: "Go home",
group: "Navigation",
icon: <HomeIcon />,
accent: "accent",
shortcut: "mod+shift+h",
perform: () => navigate("/"),
},
]);Feature parity with command-kit
- Recents + favorites, both persisted through
@stackra/storage. - Sub-page navigation via
children[]— stack nav + backspace-back. - Inline confirm dialogs with
tone: "danger". - Async
performreturning aPromise— inline spinner + toast on resolve / reject. - Draggable palette with persisted position + reset command.
- Palette theme switcher across
default/raycast/notion/linear/vercel/spotlight. - Walkthrough / onboarding overlay with step registry + progress.
- Aliases + keywords folded into fuzzy search.
- Command badges (
New/Beta/Pro) + toggle checkmarks + disabled reasons. - Analytics event fired on every run (via
@stackra/events). - Programmatic API —
runCommandById(id),openWithQuery(text). - Dynamic command sources —
useRegisterCommandSourcefor live query results (search users, files, docs).
React Native usage
The @stackra/kbd/native subpath ships the mobile counterpart of the ⌘K
palette + walkthrough experience. The compound API mirrors the web subpath —
same PaletteService, same WalkthroughService, same ShortcutService, same
ICommand shape. The only difference is the event source: NativeKbdModule
adds NativeKeyboardListener (a reserved seam for a future
react-native-keycommands bridge) instead of the web's browser
window.addEventListener("keydown", …) bridge. No service subclassing — the
cross-platform services own the state, and each platform's listener contributes
the event source.
// app.module.ts
import { Module } from "@stackra/container";
import { NativeKbdModule } from "@stackra/kbd/native";
@Module({
imports: [
NativeKbdModule.forRoot({
toggleShortcut: "mod+k",
defaultPaletteTheme: "raycast",
}),
],
})
export class AppModule {}Wire the sheet + coachmark at the top of the app shell so they float above the
route stack. <PaletteBottomSheet> reads isOpen + query from the palette
store; a floating action button in the shell (or a ⌘K chip on a tablet with a
hardware keyboard) opens it via useCommandPaletteSheet().open().
// app-shell.tsx
import {
PaletteBottomSheet,
WalkthroughCoachmark,
useCommandPaletteSheet,
} from "@stackra/kbd/native";
export function AppShell(): ReactElement {
const { open } = useCommandPaletteSheet();
return (
<>
<Navigator />
<FloatingActionButton onPress={open} label="Command palette" />
<PaletteBottomSheet />
<WalkthroughCoachmark />
</>
);
}For deep-linkable palette entry, register the full-screen variant as a navigation destination:
import { CommandPaletteSheetScreen } from "@stackra/kbd/native";
<Stack.Screen name="Palette">
{(props) => (
<CommandPaletteSheetScreen onClose={() => props.navigation.goBack()} />
)}
</Stack.Screen>;Mobile UX notes
- Hardware-keyboard shortcuts. On phone-only devices the shortcut chip (⌘K)
on each row is hidden — there's no keyboard to hit. On tablets with a paired
Bluetooth keyboard (iPad Magic Keyboard, Android tablets),
useHardwareKeyboard()detects the state heuristically and each<CommandRow>surfaces its shortcut in a trailingChip. The actual chord dispatch is a follow-up —NativeKeyboardListenerreserves the seam that will invokeShortcutService.handleKeyDown(syntheticEvent)once areact-native-keycommands(or equivalent) bridge lands. The shortcut state machine itself is cross-platform: the sameShortcutServicehandles both browser and RN dispatch. - Coachmark overlay. Mounted at the top of the app shell so the overlay renders above every route. The backdrop is non-dismissable-on-tap by design — users must use the Next / Previous / Skip buttons to advance so a mis-tap while reading doesn't collapse the tour.
- FlashList / stock FlatList.
<CommandList>uses@shopify/flash-listwhen installed and falls back to stockFlatListotherwise. Both peers are optional; no crash on missing.
Product-signal note
The command-palette pattern is well-loved on web but not established on
mobile. This subpath ships full so consumers can experiment behind a feature
flag; wait for tenant adoption signals before making it a default surface. If
product confirms the use case, promote via the tenant's features manifest per
.kiro/steering/frontend-module-architecture.md. Otherwise the subpath
tree-shakes out of consumers who never import from @stackra/kbd/native.
Testing
import { TestKbdProvider, createMockCommand } from "@stackra/kbd/testing";
test("clicks a command", async () => {
const run = vi.fn();
render(
<TestKbdProvider
commands={[createMockCommand({ id: "act", perform: run })]}
>
<MyComponent />
</TestKbdProvider>,
);
// ...
});Related steering
.kiro/steering/subpath-layering.md— this package uses Shape 1 on both platform subpaths.WebKbdModulecomposesKbdModule.forRoot(...)+ addsWebKeyboardListener(the browser event-source bridge);NativeKbdModuledoes the same withNativeKeyboardListener. The cross-platformShortcutService+WalkthroughServicein core own the state — the platform subpaths only wire the event source..kiro/steering/frontend-package-audit-checklist.md— the audit every reviewer walks per package.
