Build-time transforms inject source locations into runtime code. Alt+Click any element to jump to its source file.
\n
Try It
⋅
\n
\nHold Alt and hover over any button, then click to view its source. The mock console shows what file would open, and the viewer displays the source code.\n
\n\n
React Component Locations
⋅
\n
Transform $ prop shorthand into data-loc attributes:
\n
\n \n
\n
if (enableReact$Loc && path.endsWith(\".tsx\")) {\n for (const match of code.matchAll(CLASSNAME_$_RE)) {\n const index = match.index!;\n const [_, pre, post] = match;\n const { line, column } = indexToLineAndColumn(code, index + pre.length);\n\n const link = `${path.slice(inCodebase.index + IN_CODEBASE_SLICE)}:${line}:${column}`;\n let dataAttrs = `data-loc=${JSON.stringify(link)}`;\n if (pre.startsWith(\"<\")) {\n dataAttrs = `${dataAttrs} data-name=${JSON.stringify(pre.split(\" \")[0].slice(1).trim())}`;\n }\n\n if (post === \"=\") {\n string.overwrite(index + pre.length, index + _.length - post.length, `${dataAttrs} className`);\n } else {\n string.overwrite(index + pre.length, index + _.length - post.length, dataAttrs);\n }\n }\n }
DevStrings compose with .because() to build audit trails:
\n
const rootEvent = dev`keydown from root`;\nhandler(rootEvent.because(dev`Looking for action handler`));\n// Creates chain: "keydown from root → Looking for action handler"\n
\n
Editor Integration
⋅
\n
Local HTTP endpoint opens files:
\n
\n \n
\n
let lastOpenedFile: string | null = null;\n/**\n * uses our launch-editor endpoint to open the file in the dev's editor\n * this has been set up as a vite plugin.\n */\nexport const openInDevEditor = (loc: string) => {\n if (lastOpenedFile === loc) return;\n lastOpenedFile = loc;\n setTimeout(() => (lastOpenedFile = null), 500);\n void fetch(`http://localhost:5090/__open-in-editor?file=${loc}`).catch((error) => {\n console.error(\"Failed to open in editor\", error);\n });\n};
\n
\n
\n
openInDevEditor("Card.tsx:26:7");\n ↓\nfetch("http://localhost:5090/__open-in-editor?file=Card.tsx:26:7");\n ↓\n// Editor opens at that line\n
Let's Build Composable Keyboard Navigation Together
⋅
\n
\n
Prerequisites: We'll assume you're comfortable with React and TypeScript. We'll introduce Entity-Component-System (ECS) concepts as we go - no prior game dev experience needed!
\n
Time to read: ~15 minutes \nWhat we'll learn: How to build keyboard navigation using composable plugins that don't know about each other
\n
\n
The Problem We're Solving
⋅
\n
We're building a complex UI, and we need keyboard shortcuts everywhere. Our text editor needs Cmd+B for bold, our cards need arrow keys for navigation, our buttons need Enter to activate.
\n
We could write one giant keyboard handler that knows about every component. But we've been down that road before - it becomes a tangled mess the moment we need context-sensitive shortcuts or want to test things in isolation. Every time we add a component, we're editing that massive switch statement. Every time a shortcut conflicts, we're debugging spaghetti code.
\n
Let's try something different. We'll build a system where components declare what they need, and a plugin wires everything together automatically. No tight coupling, no spaghetti code, and every piece testable in isolation.
\n
What We're Building
⋅
\n
We'll create three interactive cards, each with different keyboard shortcuts. When we focus a card, its shortcuts become active. Press ↑/↓ to navigate between cards, then try each card's unique actions.
\n
By the end, we'll understand how four small building blocks compose into a working keyboard system - without any of them knowing about the others.
\n
Our Approach: Entities, Components, and Plugins
⋅
\n
We're borrowing a pattern from game development called Entity-Component-System (ECS):
\n
\n
Entity = A unique identifier for a thing in the system, not a class or instance
\n
Component = Data attached to an entity via a component type key
\n
Plugin = Behavior that queries entities with specific component combinations and reacts to changes
\n
\n
The mapping to React:
\n
React → ECS\n─────────────────────────────────────\nComponent instance → Entity (just a UID)\nProps/state shape → Component (data attached to UID)\nContext + useEffect → Plugin (reactive behavior)\nuseState → Atom (Jotai reactive state)\n
\n
The key insight: Components are just data. Plugins add behavior by querying for that data. Nothing is tightly coupled.
\n
Let's see this in practice.
\n
Try It Out First
⋅
\n
Before diving into theory, let's play with what we're building:
\n\n\n\n
Watch how the demo responds. Notice that only the focused card's shortcuts work - the others are \"dormant\" until focused.
\n
Now let's understand how we built this.
\n
Our Four Building Blocks
⋅
\n
Let's break down our keyboard system into four composable pieces.
\n
Block 1: Making Things Focusable
⋅
\n
First, we need to mark which entities can receive focus. We'll create a CFocusable component:
activeFocusAtom: Holds the UID of whichever entity currently has focus (or null)
\n
\n
How it connects:
\n
// When we focus a card:\nconst focusUnique = world.getUniqueOrThrow(UCurrentFocus);\nworld.store.set(focusUnique.activeFocusAtom, cardUID);\n\n// Anywhere else in our app:\nconst focusedEntityUID = world.store.get(focusUnique.activeFocusAtom);\n
\n
Notice: CFocusable and UCurrentFocus don't import each other. They communicate through atoms. The CFocusable Plugin (which we'll see soon) is what wires them together.
\n
Block 3: Declaring Actions
⋅
\n
Now we need entities to declare what keyboard actions they support:
\n
\n \n
\n
export type AnyAction = {\n label: string;\n defaultKeybinding: DefaultKeyCombo;\n description?: string;\n icon?: any;\n self?: boolean;\n hideFromLastPressed?: boolean;\n};\ntype AnyBindables = Record<string, AnyAction>;\n\nexport type ActionEvent = { target: UID };\n\nexport namespace CActions {\n export type Bindings<T extends AnyBindables> = {\n [P in keyof T]: Handler<ActionEvent> | Falsey;\n };\n}\n\nexport type ActionBindings<T extends AnyBindables = AnyBindables> = {\n bindingSource: DevString;\n registryKey: ActionRegistryKey<T>;\n bindingsAtom: Atom<CActions.Bindings<T>>;\n};\n\nexport class ActionRegistryKey<T extends AnyBindables = AnyBindables> {\n constructor(\n public readonly key: string,\n public readonly meta: { source: DevString; sectionName: string },\n public readonly bindables: T,\n ) {}\n}\n\nexport class CActions extends World.Component(\"actions\")<CActions, ActionBindings[]>() {\n static bind<T extends AnyBindables>(key: ActionBindings<T>) {\n return CActions.of([key as ActionBindings]);\n }\n\n static merge(...bindings: Array<ActionBindings | ActionBindings[]>) {\n const out: ActionBindings[] = [];\n for (const b of bindings) {\n if (Array.isArray(b)) {\n out.push(...b);\n } else {\n out.push(b);\n }\n }\n return CActions.of(out);\n }\n\n static defineActions<T extends AnyBindables>(\n key: string,\n meta: { source: DevString; sectionName: string },\n actions: T,\n ): ActionRegistryKey<T> {\n return new ActionRegistryKey(key, meta, actions);\n }\n}
\n
\n
\n
What this gives us:
\n
\n
A way to define available actions (defineActions)
\n
A way to bind handlers to those actions per entity
\n
Actions are just metadata: label, key binding, description
Notice: We defined the action schema once, then bound different handlers per entity. One card might delete, another might archive. Same action definition, different behavior.
\n
Block 4: Wiring It All Together - ActionsPlugin
⋅
\n
Here's where the magic happens. We need something that:
\n\n
Listens for keyboard events
\n
Finds the currently focused entity
\n
Matches keys to actions
\n
Executes the handler
\n\n
That's what our ActionsPlugin does:
\n
\n \n
\n
export const ActionsPlugin = World.definePlugin({\n name: dev`ActionsPlugin`,\n setup: (build) => {\n const { store } = build;\n\n // Track the currently focused entity\n const currentFocusAtom = atom((get) =>\n pipeNonNull(get(build.getUniqueAtom(UCurrentFocus)), (a) => get(a.activeFocusAtom)),\n );\n\n const rootUIDAtom = atom<UID | null>(null);\n const currentDispatchSpotAtom = atom((get) => get(currentFocusAtom) ?? get(rootUIDAtom));\n\n const handleOnce = new WeakSet<KeyboardEvent>();\n build.addUnique(UKeydownRootHandler, {\n handler(reason, keyboardEvent) {\n if (handleOnce.has(keyboardEvent)) return Outcome.Passthrough;\n handleOnce.add(keyboardEvent);\n if (keyboardEvent.defaultPrevented) return Outcome.Passthrough;\n const world = store.get(build.worldAtom);\n if (!world) return Outcome.Passthrough;\n const dispatchFromUID = store.get(currentDispatchSpotAtom);\n if (!dispatchFromUID) return Outcome.Passthrough;\n // Walk up the parent chain looking for keydown handlers\n const result = CParent.dispatch(\n dev`keydown from root`.because(reason),\n world,\n dispatchFromUID,\n CKeydownHandler,\n reason,\n keyboardEvent,\n );\n // Prevent default browser behavior when we handle the key\n if (result !== Outcome.Passthrough) {\n keyboardEvent.preventDefault();\n }\n return result;\n },\n rootUIDAtom,\n });
\n
\n
\n
Here's what happens when we press a key:
\n
User presses "X"\n ↓\nActionsPlugin.UKeydownRootHandler receives event\n ↓\nQuery: Which entity has focus? (from UCurrentFocus)\n ↓\nWalk up parent chain: Does this entity have CKeydownHandler?\n ↓\nMatch key "X" to action "delete"\n ↓\nCall the handler we bound earlier\n ↓\npreventDefault() so browser doesn't scroll\n
\n
The beautiful part? None of these components import each other. The plugin queries the world: \"Give me the focused entity. Does it have CActions? Great, wire up keyboard handling for it.\"
\n
\n \n
\n
// Provide keydown handler for entities with CActions\n build.onEntityCreated(\n {\n requires: [CActions],\n provides: [CKeydownHandler],\n },\n (uid, { actions }) => {\n const combinedCombosAtom = atom((get) => {\n type ComboData = {\n actionKey: string;\n handler: (reason: DevString, event: ActionEvent) => Outcome;\n } & AnyAction;\n\n const combosMap = new Map<string, ComboData[]>();\n\n for (const actionSet of actions) {\n const resolvedBindings = get(actionSet.bindingsAtom);\n for (const [actionKey, maybeHandler] of Object.entries(resolvedBindings)) {\n if (!maybeHandler) continue;\n const bindable = actionSet.registryKey.bindables[actionKey];\n if (!bindable) continue;\n const defaultKey = bindable.defaultKeybinding;\n const combo = normalizedKeyCombo(defaultKey, ENV_KEYBOARD_KIND).normalized;\n const comboData: ComboData = {\n actionKey,\n handler: maybeHandler,\n ...bindable,\n };\n const list = combosMap.get(combo);\n if (!list) {\n combosMap.set(combo, [comboData]);\n } else {\n list.push(comboData);\n }\n }\n }\n return combosMap;\n });\n\n const keydownHandler = CKeydownHandler.of({\n handler(reason, event) {\n // Omit shift for letter keys so \"Shift+X\" matches \"X\"\n const combos = addModifiersToKeyCombo(ENV_KEYBOARD_KIND, event, true);\n if (event.defaultPrevented) return Outcome.Passthrough;\n const combosMap = store.get(combinedCombosAtom);\n\n for (const combo of combos) {\n const comboDatas = combosMap.get(combo.normalized);\n if (!comboDatas) continue;\n for (const comboData of comboDatas) {\n const outcome = comboData.handler(dev`Key combo pressed: ${combo.normalized}`.because(reason), {\n target: uid,\n });\n if (outcome !== Outcome.Passthrough) {\n return outcome;\n }\n }\n }\n return Outcome.Passthrough;\n },\n });\n\n return { keydownHandler };\n },\n );
\n
\n
\n
This is the actual per-entity handler creation. When we add an entity with CActions, the plugin automatically:
\n
\n
Reads all action definitions
\n
Normalizes key combos (so \"X\" and \"Shift-X\" both match)
\n
Creates a CKeydownHandler that matches keys to handlers
\n
Plugs it into the event system
\n
\n
We don't call any of this ourselves. It Just Works™.
\n
What We Learned
⋅
\n
Let's step back and appreciate what we built:
\n
✅ We Can Test Everything In Isolation
⋅
\n
Want to test if \"X\" triggers delete? No React needed:
\n
const world = createTestWorld();\nconst cardUID = addCardEntity(world, {\n onDelete: mockFn,\n});\n\n// Simulate focus\nworld.store.set(focusAtom, cardUID);\n\n// Simulate keypress\nrootHandler.handler(dev`test`, { key: "x" });\n\nexpect(mockFn).toHaveBeenCalled();\n
\n
✅ Components Are Composable
⋅
\n
A simple button might only have CFocusable. A rich text editor adds CActions with 50 shortcuts. A card adds both plus CSelectable. Mix and match:
For our use case (complex editor with nested contexts), the composition benefits outweigh the complexity cost. For most apps, a 2KB library like tinykeys is probably the right call.
\n
Tracing a Keypress Together
⋅
\n
Let's walk through exactly what happens when we press \"X\" to delete a card. This demystifies the \"magic\":
\n
📍 Step 1: DOM Event (keyboard-demo.entrypoint.tsx:29)\n document.addEventListener('keydown', ...)\n Event fires with event.key = "x"\n\n ↓\n\n📍 Step 2: Root Handler (ActionsPlugin.ts:33-42)\n UKeydownRootHandler.handler() receives event\n Check currentDispatchSpotAtom: Is anything focused?\n Result: Card 2 has focus\n\n ↓\n\n📍 Step 3: Parent Walk (ActionsPlugin.ts:44-55)\n CParent.dispatch() walks up the entity tree\n Current: Card 2 → Does it have CKeydownHandler? YES ✓\n\n ↓\n\n📍 Step 4: Key Normalization (ActionsPlugin.ts:105-107)\n addModifiersToKeyCombo("x", event, omitShift=true)\n "x" → "X" (uppercase)\n No modifiers, final combo: "X"\n\n ↓\n\n📍 Step 5: Action Lookup (ActionsPlugin.ts:110-115)\n combosMap.get("X") → [{action: "delete", handler: fn}]\n Call handler(dev`Key combo`, {target: card2UID})\n\n ↓\n\n📍 Step 6: Our Handler (createKeyboardDemo.ts:83)\n onDelete() runs\n alert("Deleted Card 2!")\n Returns: handled`delete`\n\n ↓\n\n📍 Step 7: Prevent Default (ActionsPlugin.ts:50-52)\n outcome !== Passthrough, so:\n event.preventDefault() ← stops browser scroll\n Return "handled" to stop propagation\n
\n
Key insight: Notice how information flows through atoms and component queries, never through direct imports or method calls. That's the decoupling in action.
\n
What's Next?
⋅
\n
Now that we understand composable keyboard navigation, we can:
\n
\n
Add spatial navigation (arrow keys navigate a 2D grid)
\n
Build focus trapping for modals
\n
Create a command palette with searchable actions
\n
Support user-customizable keybindings
\n
\n
The pattern scales because we're composing data, not coupling objects.
\n
Reflection
⋅
\n
We started with a problem: keyboard shortcuts without spaghetti code.
\n
We solved it by separating concerns:
\n
\n
CFocusable says \"I can receive focus\" (data)
\n
CActions says \"I have these shortcuts\" (data)
\n
ActionsPlugin says \"When those exist together, wire them up\" (behavior)
\n
\n
No component knows about the others. Add a new shortcut? Update one entity's CActions. Add a new focusable element? Add CFocusable. The plugin handles the rest.
\n
That's the power of Entity-Component-System for UI.