{"id":23550028,"url":"https://github.com/edwardsharp/redux-key-nav","last_synced_at":"2026-07-11T05:31:41.430Z","repository":{"id":139692552,"uuid":"522367184","full_name":"edwardsharp/redux-key-nav","owner":"edwardsharp","description":"keyboard navigation concept","archived":false,"fork":false,"pushed_at":"2022-08-09T16:35:58.000Z","size":1642,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-26T12:45:49.208Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://edwardsharp.github.io/redux-key-nav/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edwardsharp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-08-08T01:23:12.000Z","updated_at":"2022-08-08T01:36:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"e768b55c-66af-40b0-beb7-c54ff3b9fbf5","html_url":"https://github.com/edwardsharp/redux-key-nav","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/edwardsharp/redux-key-nav","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edwardsharp%2Fredux-key-nav","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edwardsharp%2Fredux-key-nav/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edwardsharp%2Fredux-key-nav/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edwardsharp%2Fredux-key-nav/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edwardsharp","download_url":"https://codeload.github.com/edwardsharp/redux-key-nav/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edwardsharp%2Fredux-key-nav/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35352623,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-11T02:00:05.354Z","response_time":104,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-26T10:12:44.214Z","updated_at":"2026-07-11T05:31:41.417Z","avatar_url":"https://github.com/edwardsharp.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# redux-key-nav\n\nan opinionated proof-of-concept \u0026 demo for handling arrow key navigation in React + Redux apps.\n\n**note:** this is very much a work in progress. this repo isn't currently intended to be a library or finished product but rather my own explorations in a particular problem space. may it be an inspiration for how you do (or don't) implement keyboard navigation in your own webapp.\n\n## high-level goals \u0026 tradeoffs\n\n1. avoid dynamic (auto-magical) inference of rendered artifacts (i.e. the DOM) **rather:** attempt to be explicit in declaring all navigable elements and their behavior.\n2. avoid reading the DOM (querySelectors, getBoundingClientRect, etc.) **rather:** lean into Redux; try to align data flow to move in a single direction.\n3. avoid trying to be an all-in-one, handles-every-use-case-library solution **rather:** lean into the particular needs, use cases, biz logic etc. of the application at hand.\n4. avoid black-box encapsulation **rather:** embrace extensibility for edge cases (but try to settle on sensible default behavior).\n\n## overview\n\n### 🧃 containers\n\na core tenant of this is to explicitly represent all the containers that will be used in the app. each container represents a single row or column of navigable items. containers can contain other containers. like how flexbox handles rows and columns.\n\nat the heart of this all is a static definition of all the containers that will be used in the app, `navigationContainers` (see: [src/lib/navigation/navigation.ts](src/lib/navigation/navigation.ts)) is an object where the object key names represent the names of each unique container (`NavigationContainerName`) and value(s) like:\n\n```ts\ninterface NavigationContainer {\n  direction: \"row\" | \"column\";\n  exits?: {\n    north?: NavigationContainerName;\n    south?: NavigationContainerName;\n    east?: NavigationContainerName;\n    west?: NavigationContainerName;\n  };\n}\n```\n\n`row` correlates to left \u0026 right arrow keys and `column` to up and down. `exits` reference other containers that navigation move to next after reaching the boundary. as an example suppose we have 6 buttons in two containers (one row and one column) like so:\n\n```\n┌──────────────────┐\n│                  │ topContainer: {\n│ ┌────┬─────┬───┐ │   direction: \"row\",\n│ │  a │  b  │ c │ │   exits: { south: \"mainContainer\" }\n│ └────┴─────┴───┘ │ },\n│                  │\n│ ┌──────────────┐ │\n│ │    one       │ │ mainContainer: {\n│ ├──────────────┤ │   direction: \"column\",\n│ │    two       │ │   exits: { north: \"topContainer\" }\n│ ├──────────────┤ │ }\n│ │    three     │ │\n│ └──────────────┘ │\n│                  │\n└──────────────────┘\n\n```\n\nleft and right arrow keys will navigate between `a`, `b`, or `c` and the down arrow (when on any button in this row) will exit to `mainContainer` and here up and down arrows will navigate between `one`, `two`, and `three` buttons and the up arrow (when on button `one`) will exit to `topContainer`.\n\n### 🪝 component hooks\n\n`useNavigationKeys` [src/lib/navigation/navigation.hooks.ts](src/lib/navigation/navigation.hooks.ts) should be called once as far up the React component tree as possible ([App.tsx](src/App.tsx)). this is the global key event handler that dispatches events to move focus selection when arrow keys are pressed. it will also publish events (using a simple pub/sub abstraction) for `enter` key presses that will call the `onSelect` fn passed to `useNavigation` (this would typically be the `onClick` handler your button already uses).\n\n`useNavigation` [src/lib/navigation/navigation.hooks.ts](src/lib/navigation/navigation.hooks.ts) can mixed into any navigable components like buttons, inputs, selects, etc. basically anything that has click (or other key/mouse) handlers. this hook will take care of registering items in the Redux store and subscribing to `enter` (or possibly other) key presses.\n\na very simple example would be like so:\n\n```ts\nfunction SimpleButton() {\n  const { isActiveElement } = useNavigation({\n    name: \"a\",\n    position: 0,\n    containerId: \"topContainer\",\n    onSelect: (arg?: string) =\u003e {\n      console.log(\"got event with arg:\", arg);\n    },\n  });\n\n  return (\n    \u003cbutton onClick={(e) =\u003e onSelect(\"on click!\")}\u003e\n      {isActiveElement ? \"an active\" : \"a\"} button\n    \u003c/button\u003e\n  );\n}\n```\n\n### misc.\n\nthere's logic to handle more grid-like structures, so for example:\n\n```\n┌───────────────────────┐\n│                       │\n│ ┌────────┐ ┌────────┐ │\n│ │   a    │ │  one   │ │\n│ ├────────┤ ├────────┤ │\n│ │   b    │ │  two   │ │\n│ ├────────┤ ├────────┤ │\n│ │   c    │ │  three │ │\n│ └────────┘ └────────┘ │\n│                       │\n└───────────────────────┘\n```\n\npressing the right arrow while on `a` will exit to `one`, `b` -\u003e `two` and `c` -\u003e `three` (as well as the inverse when pressing left in column 2 to get back to buttons on the same row in column 1).\n\n`initialFocus` will cause that item to get set as the active item if there isn't already one.\n\n`focusOnMount` will get set as the active item no matter if one already exists (see: [src/examples/popovers/Popovers.tsx](src/examples/popovers/Popovers.tsx)).\n\n`onExitContainer` is intended to be used when there are two (or more) containers that are rendered but do not reference each other with their container's `exits` property. an example is a navigation trap like a modal dialog popup that renders atop the other navigation items but navigation should be constrained to that container (or containers) only and when the container is closed call back to `onExitContainer` with the container name that active focus should go to (see: [src/examples/popovers/Popovers.tsx](src/examples/popovers/Popovers.tsx)).\n\nuse a `ref` in order to call methods on the HTMLElement like `.focus()` when an item is focused (i.e. `useEffect(()=\u003e ref.focus(), [activeItem])` or when enter (or whatever) key presses happen. see: [src/examples/infinite/Infinite.tsx](src/examples/infinite/Infinite.tsx)\n\nplease see [src/examples/](src/examples/) for more.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedwardsharp%2Fredux-key-nav","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedwardsharp%2Fredux-key-nav","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedwardsharp%2Fredux-key-nav/lists"}