Keyboard navigation
Comprehensive keyboard shortcuts and navigation across the app — move between session rows, open/close detail, trigger common actions, jump between pages — built on a central, scoped, registry-driven shortcut system that auto-generates a searchable help cheat-sheet.
Part of the Arkime 7 UI epic (#3926). Large. Related prior art: #951 (closed a11y spec — the source of the in-view nav wishlist), #841 (original global-nav shortcuts), #2271 (cont3xt counterpart — will adopt this framework later).
Decisions (settled)
| Area | Decision |
|---|---|
| Activation | Hybrid. Shift+letter = global nav/focus (today's model, kept). Bare keys (j/k, arrows, Enter, x…) = in-view item navigation. Bare keys fire only when focus is not in an input/select/textarea. |
| Architecture | Central registry/service. Views register shortcuts (keys, scope, descKey, handler); the help UI is generated from the registry; descriptions are i18n'd; scope-awareness keeps Sessions keys from firing on other views. Replaces the App.vue switch + hardcoded help slot. |
| v1 coverage | Sessions-focused — full Sessions in-view nav + polished global nav + generated help. SPIView / SPIGraph / Connections / Files deep nav deferred to follow-ups. |
| Reach | Framework in common/vueapp, viewer wired in v1. Cont3xt (#2271), parliament, WISE adopt the same framework later with their own maps. |
| Nav keys | Both j/k and ↑/↓ move focus (rows, and later sections). |
| Discoverability | Full searchable cheat-sheet modal opened with ? — categorized (Global / Sessions / Detail), with a filter box. Replaces the current slide-in panel. |
| Customization | Fixed default map for v1 (no remap UI). Registry keeps future user-remapping cheap to add. |
What exists today (we build on / refactor this)
common/vueapp/KeyboardShortcuts.vue— Shift-hold detection (shiftKeyHold), the slide-in?overlay, esc/outside-click close. Shared; also used by cont3xt.viewer/vueapp/src/App.vue— one bigkeyupswitchmapping Shift+letter → route/focus, plus the help text hardcoded in English in a template slot.- Current global map (Shift held):
Qfocus query ·Tfocus time ·S/A/V/G/C/H/Ujump to Sessions/Arkime/SPIView/SPIGraph/Connections/Help/Hunt ·Shift+Entersearch ·?help ·Escblur/close.
Gap: everything today is global nav/focus. There is no in-view navigation (no focused-row concept, no keyboard detail toggle, no section/page nav). The Sessions table expands detail inline on click; pagination is the shared Vuetify Pagination.vue with no key support.
Architecture — central registry in common/
A single keyboard service replaces the scattered window listeners and the App.vue switch.
// common/vueapp/keyboard/registry (singleton + composable)
register({
keys: ['j', 'ArrowDown'],
scope: 'sessions', // 'global' | view name
descKey: 'shortcuts.sessions.nextRow',
handler: nextRow,
when: () => rowsExist, // optional guard
});- One global
keydown/keyuplistener in the service. Input-guard: bare keys never fire while focus is ininput/select/textarea; Shift+letter guarded as today. - Scope resolution: active scope derives from the current route (+ optionally the focused component). Global shortcuts always live; view shortcuts only when their scope is active.
- Shift-hold stays the trigger for the
?affordance and allShift+letter global keys (reuse current detection, moved into the service). - Help generation: the cheat-sheet modal reads the registry, groups by scope, renders i18n descriptions → never drifts.
- Migration: refactor
KeyboardShortcuts.vueto be registry-driven; port theApp.vueswitch intoglobal-scope registrations; delete the hardcoded help slot. Cont3xt must keep working — either leave it on the current minimal path until it adopts the framework, or migrate it as part of #2271 (coordinate; this is a shared-file refactor).
The shortcut map
Global (Shift held) — retained from today
Shift+Q focus query · Shift+T focus time · Shift+S/A/V/G/C/H/U jump to pages · Shift+Enter run search · Shift+P focus page picker (new, from #951) · ? help · Esc blur/close.
Sessions — in-view (bare keys, only when not in an input)
| Keys | Action |
|---|---|
j / k / ↑ / ↓ |
focus next / prev session row |
Enter |
open / close detail for the focused row |
Esc |
close detail / clear row focus (layers under the app-wide blur) |
x |
toggle-select the focused row (bulk actions) |
a |
open the focused row's actions menu |
O |
open / close all rows' detail (existing openAll/closeAll) |
] / [ |
jump to next / prev meta section in the open detail (HTTP, DNS…) |
n / p |
next / prev page |
Conflict check (the ticket's third open question — resolved)
cstays global Connections under Shift; barecis unused in-view → no clash.- Bare
a/O/x/n/p/j/kare distinct from theirShift+letter global counterparts (different modifier state) → no clash. - Bare
Enter(open detail) vsShift+Enter(global search) → distinct. Escbehavior layers: app-wide input-blur first; if a row is focused with no input active, also close detail / clear focus.
Implementation reality — Sessions focused-row mechanics
New work on the Sessions table (the bulk of v1):
- A
focusedIndexstate + visible focus ring, scroll-into-view, and roving-tabindex for a11y. Entertoggles the existing inline detail for the focused row;Oreuses existing open/close-all.aopens the existing per-row actions menu programmatically.] / [requiresSessionDetail.vueto expose ordered section anchors to navigate between.n / p+Shift+Prequires adding key hooks / a focus target to the sharedPagination.vue.
Components (new / changed)
common/vueapp/keyboard/(new) — registry singleton +useKeyboardShortcutscomposable + the single global listener.common/vueapp/ShortcutsCheatSheet.vue(new) — searchable, categorized?modal generated from the registry.common/vueapp/KeyboardShortcuts.vue(refactor) — becomes registry-driven; shift detection delegated to the service; slide-in replaced by the cheat-sheet modal.viewer/.../App.vue(refactor) — remove the switch + hardcoded help; register global shortcuts via the service.viewer/.../sessions/Sessions.vue(change) — focused-row state, focus ring, scroll-into-view, register Sessions-scope shortcuts.viewer/.../sessions/SessionDetail.vue(change) — expose section anchors for] / [.common/vueapp/Pagination.vue(change) —n/p/Shift+Phooks.- i18n —
shortcuts.*description keys acrosscommon/vueapp/locales/*(11 languages).
Phasing
- Registry + service in common; port today's global map into it; no behavior change for users yet.
- Cheat-sheet modal generated from the registry; retire the hardcoded slide-in help.
- Sessions focused-row foundation — focusedIndex, focus ring, scroll-into-view, a11y roving tabindex.
- Sessions actions — Enter/Esc detail, x select, a actions menu, O all,
]/[sections. - Pagination keys +
Shift+P. - Polish — a11y pass, i18n strings, conflict QA, UI review.
Out of scope for v1
- In-view nav for SPIView (header navigate/expand/collapse), SPIGraph (key-box focus), Connections (graph pan/zoom/node-walk), Files (pagination) — follow-ups per #951.
- User remapping / a Shortcuts settings tab.
- Cont3xt (#2271) / parliament / WISE wiring — they adopt the common framework later.
Remaining open questions (small)
- Scope resolution source of truth: route-only vs. route + focused-component (matters once a view has multiple focusable regions).
Esclayering exact order when both an input is focused and a detail row is open.- Whether
?should also be reachable without holding Shift (i.e. bare?=Shift+/already) — confirm the key event we bind. - Focus-ring visual treatment + whether the focused row should also become the keyboard target for native Tab.
Source: arkime/arkime