CtrlK Documentation

The first IOUX (Integrated Operational UX) for enterprise web apps. 19 modules, 150 tests, zero dependencies.

CtrlK is an interaction layer that sits between your users and your application. It adds command palette, keyboard shortcuts, saved views, persistent selections, field navigation, macros, undo, and shareable view links — on top of any existing enterprise web app. No redesign required.

What is an IOUX? An IOUX (Integrated Operational UX) is to application operators what an IDE is to developers. Where an IDE integrates editing, compiling, and debugging into a development workspace, an IOUX integrates commands, views, shortcuts, macros, and history into an operational workspace.

Installation

terminalShell
# npm
npm install @ctrlk/core

# yarn
yarn add @ctrlk/core

# CDN (Pattern A drop-in)
<script src="https://unpkg.com/ctrlk/dist/ctrlk.runtime.min.js"></script>

Framework-specific packages:

terminalShell
npm install @ctrlk/react       # React hooks + Provider
npm install @ctrlk/angular     # Service + directives
npm install @ctrlk/ag-grid     # AG Grid adapter

Quick Start

30-Second Setup (Pattern A)

index.htmlHTML
<!-- Add before </body>. That's it. -->
<script src="ctrlk.runtime.min.js"></script>

<!-- Result:
  · Ctrl+K opens command palette
  · All buttons auto-discovered as commands
  · Ctrl+D cycles density
  · Every section and link is searchable -->

Programmatic Setup (Pattern B)

app.jsJavaScript
import ctrlk from 'ctrlk';

// Initialize with options
ctrlk.init({
  palette: true,          // Enable command palette UI
  density: true,          // Enable density controller
  autoDiscover: true,     // Auto-register DOM elements
  session: true,          // Enable session tracking
  macros: true,           // Enable macro recording
  history: true,          // Enable undo/redo
});

// Register a command
ctrlk.commands.register({
  id: 'filter.active',
  title: 'Show Active Only',
  shortcut: 'Alt+A',
  category: 'Filters',
  icon: '✅',
  execute: () => applyFilter('active'),
});

Integration Patterns

PatternSetup TimeIntegration DepthBest For
A — Drop-In30 secondsAuto-discovery onlyQuick evaluation, legacy apps
B — Declarative1-2 hoursCustom commands + viewsMost applications
C — FrameworkHalf dayHooks, directives, adaptersReact/Angular/Vue apps with grid libraries

Each pattern extends the previous. Start with A, evolve to C over time. No migration or rewrite needed.

EventBus Core

The internal event system. All modules communicate through this bus. You rarely call it directly, but you can subscribe to any event for custom behavior.

MethodParametersReturnsDescription
on(event, handler)string, FunctionFunction (unsubscribe)Subscribe to an event. Returns cleanup function.
emit(event, data)string, anyvoidEmit an event with optional data payload.
off(event?)string?voidRemove all listeners for an event, or all events if no argument.
exampleJavaScript
// Listen for any command execution
const unsub = ctrlk.bus.on('command:executed', ({ id }) => {
  console.log(`Command executed: ${id}`);
});

// Later: clean up
unsub();

CommandRegistry Core

The central registry of all commands. Every action in CtrlK is a command — palette entries, shortcuts, macro steps, and history entries all reference commands by ID.

MethodParametersReturnsDescription
register(definition)CommandDefFunction (unregister)Register a command. Returns cleanup function.
execute(id, ...args)string, ...anyanyExecute a command by ID.
search(query)stringCommandDef[]Fuzzy search commands by title or ID.
get(id)stringCommandDef?Get a command definition by ID.
has(id)stringbooleanCheck if a command exists.
list(category?)string?CommandDef[]List all commands, optionally filtered by category.
unregister(id)stringbooleanRemove a command by ID.

CommandDef Object

PropertyTypeRequiredDescription
idstringYesUnique command ID (e.g. 'view.save')
titlestringYesDisplay title in palette
executeFunctionYesFunction to run when command is invoked
shortcutstringNoKeyboard shortcut (e.g. 'Ctrl+Shift+S')
categorystringNoGroup label in palette (e.g. 'Views')
iconstringNoEmoji or icon for palette display
undoFunctionNoUndo function (enables Ctrl+Z for this command)
whenFunctionNoCondition function — command only available when this returns true

Events emitted: command:registered command:executed command:unregistered

ShortcutEngine Core

Scope-aware keyboard shortcut binding. Supports modifier keys, key chords (sequences), and scope-based activation (e.g. a shortcut only active when a grid is focused).

MethodParametersReturnsDescription
bind(shortcut, commandId)string, stringFunction (unbind)Bind a shortcut to a command.
unbind(shortcut)stringvoidRemove a shortcut binding.
setScope(scope)stringvoidSet the active scope (e.g. 'grid', 'detail').
getScope()stringGet the current scope.

Shortcut Format

examplesText
'Ctrl+K'           // Single shortcut
'Ctrl+Shift+S'     // Multiple modifiers
'Alt+1'            // Alt + number
'F2'               // Function key
'Ctrl+K Ctrl+S'    // Chord (press Ctrl+K, then Ctrl+S)
'Escape'           // Named key

Events emitted: shortcut:fired shortcut:chord-started

CommandPalette Core

The Ctrl+K searchable command UI. Renders a floating overlay with fuzzy search, keyboard navigation, category grouping, and shortcut display.

MethodDescription
inject()Create and inject the palette DOM elements into the page.
show()Open the palette.
hide()Close the palette.
toggle()Toggle open/close.
isOpen()Returns true if palette is currently visible.

Events emitted: palette:opened palette:closed palette:selected

DensityController Core

Controls information density across the application. Cycles between compact, comfortable, and spacious by setting CSS custom properties on the document root.

MethodDescription
init()Initialize with default level (reads from localStorage if saved).
cycle()Advance to the next density level.
set(level)Set a specific level: 'compact', 'comfortable', or 'spacious'.
get()Returns the current density level string.

CSS Custom Properties Set

PropertyCompactComfortableSpacious
--ctrlk-densitycompactcomfortablespacious
--ctrlk-row-height28px36px48px
--ctrlk-cell-padding2px 8px6px 12px10px 16px
--ctrlk-font-size12px14px16px

Events emitted: density:changed

AutoDiscovery Core

Scans the DOM and auto-registers elements as commands. Buttons get their text content as the title, links get their href, sections get navigation commands. This is what makes Pattern A work with zero configuration.

MethodDescription
start()Begin scanning and watching for DOM changes (uses MutationObserver).
stop()Stop watching for DOM changes.
rescan()Manually re-scan the DOM for new elements.
Pattern A in one sentence: include the script → AutoDiscovery scans all <button>, <a>, and [data-ctrlk] elements → registers them as searchable commands in the palette.

ViewStateManager State

Save, restore, and share complete application view states. A "view" captures grid columns, filters, sort, scroll position, density, and any custom app state. Default limit: 5 saved views with LRU eviction.

MethodParametersReturnsDescription
save(name, options?)string, {description?, scope?, overwrite?}ViewStateSave current state as a named view. Evicts LRU if at max.
load(name)stringbooleanRestore a saved view. Updates lastUsed timestamp.
has(name)stringbooleanCheck if a named view exists.
get(name)stringViewState?Get a saved view without applying it.
delete(name)stringbooleanDelete a saved view.
list()ViewState[]List all saved views.
getSlots()ArrayAll views with slot numbers and shortcuts (Ctrl+1 through Ctrl+N).
capture()ObjectCapture current state (grid + app providers) without saving.
autoSave()voidAuto-save current state (restored on next init).
autoRestore()booleanRestore the auto-saved state.
setMaxViews(n)numbervoidChange max saved views. Evicts if currently over limit.
getMaxViews()numberGet current max (default: 5).
setGridAdapter(adapter)GridAdaptervoidConnect a grid adapter for state capture/restore.
registerProvider(key, provider)string, {capture, restore}FunctionRegister custom app state. Returns unregister function.

Constructor Options

optionsJavaScript
new ViewStateManager(bus, {
  maxViews: 5,   // Default: 5. LRU eviction when exceeded.
});

view:saved Event Payload

event dataJavaScript
{
  name: 'Monday Review',
  slot: 2,                    // Position (1-based)
  shortcut: 'Ctrl+2',          // Assigned shortcut (null if slot > 9)
  totalSaved: 3,               // Current count
  maxViews: 5,                 // Configured max
  remaining: 2,                // Slots available
  evicted: 'Old View',         // Name of evicted view (null if none)
}

Events: view:saved view:loaded view:deleted view:evicted

SelectionModel State

Cross-page persistent selections with named sets and set operations. Selections survive pagination, filtering, and navigation.

MethodDescription
select(ids)Add IDs to the current selection.
deselect(ids)Remove IDs from the current selection.
toggle(id)Toggle a single ID.
clear()Clear the entire selection.
getSelected()Returns Set of all selected IDs.
count()Number of selected items.
isSelected(id)Check if a specific ID is selected.
where(predicate)Select by expression: .where(row => row.status === 'active')
saveAs(name)Save the current selection as a named set.
loadSet(name)Restore a named selection set.
union(setName)Current selection ∪ named set.
intersect(setName)Current selection ∩ named set.
subtract(setName)Current selection − named set.

Events: selection:changed selection:cleared selection:set-saved

FieldRegistry State

Field-level navigation and tracking for detail pages. Registers form fields for jump-to, dirty tracking, empty navigation, and pinning.

MethodDescription
register(fieldId, options)Register a field with label, section, element ref.
jumpTo(fieldId)Scroll to and focus a field.
nextEmpty()Jump to the next empty/unfilled field.
getDirty()Returns list of fields that differ from original values.
pin(fieldId)Pin a field for cross-record comparison.
unpin(fieldId)Unpin a field.
getPinned()Returns all pinned field IDs.
search(query)Search fields by label text.
getCompleteness()Returns { total, filled, empty, percent }.

Events: field:focused field:dirty field:pinned

GridAdapter Adapter

Abstract interface that grid-specific adapters implement. This is the contract between CtrlK and any grid library.

MethodDescription
captureState()Returns the full grid state: columns, filters, sort, scroll.
restoreState(state)Apply a previously captured state.
getColumns()Returns array of column definitions with visibility.
setColumnVisible(colId, visible)Show/hide a column.
ensureColumnVisible(colId)Scroll to make a column visible.
getSelectedRows()Returns array of selected row IDs.
flashCells(options)Flash-highlight cells (for column jump feedback).
Grid adapter implementations: @ctrlk/ag-grid and @ctrlk/devextreme implement this interface. The same CtrlK commands work with either grid — the adapter translates.

MacroEngine Power

Record, parameterize, and replay command sequences. A macro is a named list of commands that can be replayed with a single keystroke.

MethodDescription
startRecording(name)Begin recording. All subsequent command executions are captured.
stopRecording()Stop recording and save the macro.
play(name, params?)Replay a saved macro. Optional parameter substitution.
list()List all saved macros.
delete(name)Delete a saved macro.
isRecording()Returns true if currently recording.

Events: macro:recording-started macro:recording-stopped macro:played

HistoryManager Power

Application-level undo/redo with branching. Tracks commands that define an undo function and allows reversal.

MethodDescription
undo()Undo the last undoable command.
redo()Redo the last undone command.
canUndo()Returns true if undo is available.
canRedo()Returns true if redo is available.
getHistory()Returns the full history stack.
clear()Clear all history.

Events: history:undo history:redo history:pushed

ActiveFilterBar Power

Renders dismissible filter chips showing all active filters. Each chip has a × button to remove that filter.

MethodDescription
addFilter(key, label, removeFn)Add a filter chip. removeFn is called when × is clicked.
removeFilter(key)Programmatically remove a filter chip.
clear()Remove all filter chips.
getActive()Returns array of active filter keys.

ColumnNavigator Nav

Column search, bookmarks, and horizontal navigation. The module that solves Problem #1 — finding columns in a 150-column grid.

MethodDescription
search(query)Search columns by name. Returns matching column definitions.
jumpTo(colId)Scroll horizontally to a column and flash-highlight it.
bookmark(colId)Toggle bookmark on a column.
nextBookmark()Jump to the next bookmarked column.
prevBookmark()Jump to the previous bookmarked column.
getBookmarks()Returns all bookmarked column IDs.
setVisibilityProfile(name, colIds)Define a named column profile.
applyProfile(name)Apply a column visibility profile.

Default shortcuts: Ctrl+G (search), Ctrl+→ / Ctrl+← (bookmark nav)

FocusNavigator Nav

Spatial zone navigation. Defines zones (toolbar, grid, sidebar, detail panel) and cycles between them with F6.

MethodDescription
addZone(id, element, options?)Register a focus zone.
nextZone()Move focus to the next zone (F6).
prevZone()Move focus to the previous zone (Shift+F6).
focusZone(id)Focus a specific zone by ID.
discover()Auto-discover zones from [data-ctrlk-zone] attributes.

SessionTracker Nav

Track which records have been visited, reviewed, or marked dirty in a batch workflow. Shows progress (e.g. "12/30 reviewed").

MethodDescription
markVisited(id)Mark a record as visited.
markReviewed(id)Mark as reviewed (subset of visited).
markDirty(id)Mark as having unsaved changes.
nextUnreviewed()Returns the ID of the next unreviewed record.
getProgress()Returns { visited, reviewed, dirty, total, percent }.
isReviewed(id)Check if a specific record is reviewed.
reset()Clear all tracking data.

Events: session:visited session:reviewed session:progress

ViewShare New

Shareable application views. Three tiers: URL links (no server), stored team views (app provides storage), and live sharing (Enterprise).

Tier 1 — URL Links (no server)

MethodDescription
createLink(options?)Compress current view state into a shareable URL. Options: { name?, description? }
applyFromUrl()Read #ctrlk= hash from current URL and apply the state.
copyLink(options?)Create link and copy to clipboard.
peekLink(url)Extract metadata from a shared URL without applying it.

Tier 2 — Stored Sharing (app provides storage)

MethodDescription
setProvider(provider)Set storage backend: { save, load, list, delete }
publish(name, options?)Save view to shared store. Options: { scope?, expiresIn? }
load(viewId)Load a shared view by ID. Tracks usage count.
list(options?)List shared views. Sort by name, date, or useCount.
remove(viewId)Delete a shared view.
createLocalProvider(ns?)Built-in localStorage provider for testing.

Tier 3 — Live Sharing (Enterprise)

MethodDescription
startBroadcast()Start broadcasting view state changes.
stopBroadcast()Stop broadcasting.
follow(userId)Follow another user's view in real-time.
stopFollow()Stop following.

Events: share:link-created share:published share:loaded share:broadcast-start

React Integration Framework

Setup

App.jsxReact
import { CtrlKProvider } from '@ctrlk/react';
import ctrlk from 'ctrlk';

function App() {
  return (
    <CtrlKProvider instance={ctrlk}>
      <YourApp />
    </CtrlKProvider>
  );
}

Available Hooks

HookDescription
useCtrlkCommand(def, deps)Register a command. Auto-unregisters on unmount.
useCtrlkView(key, default)Two-way binding to a view state value.
useCtrlkDensity()Returns current density level.
useCtrlkSelection()Returns [selected, { select, deselect, toggle, clear }].
useCtrlkField(fieldId, options)Register a form field for jump-to and tracking.
useCtrlkShortcut(shortcut, handler)Bind a shortcut inside a component.
useCtrlk()Access the raw CtrlK instance.
Component.jsxReact
import { useCtrlkCommand, useCtrlkView } from '@ctrlk/react';

function DataGrid() {
  // Command auto-registers, auto-unregisters on unmount
  useCtrlkCommand({
    id: 'grid.refresh',
    title: 'Refresh Data',
    shortcut: 'Ctrl+R',
    execute: () => fetchData(),
  }, []);

  // View state survives navigation
  const [filters, setFilters] = useCtrlkView('grid.filters', {});

  return <Grid filters={filters} />;
}

Angular Integration Framework

Setup

app.module.tsAngular
import { CtrlkModule } from '@ctrlk/angular';

@NgModule({
  imports: [CtrlkModule.forRoot()],
})

Available Directives

DirectiveDescription
[ctrlkCommand]Register a command from a template element.
[ctrlkField]Register a form field for jump-to navigation.
[ctrlkShortcut]Bind a shortcut to a component method.
[ctrlkZone]Define a focus navigation zone.
component.htmlAngular
<!-- Register commands from templates -->
<button
  ctrlkCommand="export.csv"
  ctrlkCommandTitle="Export to CSV"
  ctrlkCommandShortcut="Ctrl+Shift+E"
  (click)="exportCsv()">
  Export
</button>

<!-- Register fields for jump-to -->
<input
  ctrlkField="patient.name"
  ctrlkFieldLabel="Patient Name"
  ctrlkFieldSection="Demographics"
  [(ngModel)]="patient.name" />

Vue 3 Integration Framework

composables/useCtrlk.jsVue 3
import { onMounted, onUnmounted } from 'vue';
import ctrlk from 'ctrlk';

export function useCtrlkCommand(def) {
  let unregister;
  onMounted(() => { unregister = ctrlk.commands.register(def); });
  onUnmounted(() => { unregister?.(); });
}

export function useCtrlkField(fieldId, options) {
  onMounted(() => { ctrlk.fields.register(fieldId, options); });
}

AG Grid Adapter Adapter

setupJavaScript
import { AgGridAdapter } from '@ctrlk/ag-grid';

gridOptions.onGridReady = (params) => {
  const adapter = new AgGridAdapter(params.api);
  ctrlk.views.setGridAdapter(adapter);
};

API Translation

CtrlK OperationAG Grid API Called
Capture stategridApi.getColumnState() + getFilterModel()
Restore stategridApi.applyColumnState() + setFilterModel()
Show/hide columnapplyColumnState({ state: [{ colId, hide }] })
Scroll to columngridApi.ensureColumnVisible(colId)
Highlight columngridApi.flashCells({ columns: [colId] })
Set row heightupdateGridOptions({ rowHeight }) + resetRowHeights()
Export CSVgridApi.exportDataAsCsv()

DevExtreme Adapter Adapter

setupJavaScript
const grid = new DevExpress.ui.dxDataGrid(el, { ... });

// DevExtreme has a built-in state() method
const adapter = {
  captureState: () => grid.state(),
  restoreState: (s) => grid.state(s),
  setColumnVisible: (f, v) => grid.columnOption(f, 'visible', v),
  ensureColumnVisible: (f) => {
    const idx = grid.getVisibleColumnIndex(f);
    grid.getScrollable()?.scrollToElement(/*...*/);
  },
};
ctrlk.views.setGridAdapter(adapter);

Configuration

full configJavaScript
ctrlk.init({
  // Feature toggles
  palette: true,            // Enable command palette UI
  density: true,            // Enable density controller
  autoDiscover: true,       // Auto-register DOM elements
  session: true,            // Enable session tracking
  macros: true,             // Enable macro recording
  history: true,            // Enable undo/redo

  // Shortcuts
  paletteShortcut: 'Ctrl+K',
  densityCycleShortcut: 'Ctrl+D',

  // ViewStateManager
  maxViews: 5,              // Max saved views (LRU eviction)
});

Events Reference

EventModulePayload
command:registeredCommandRegistry{ id, title }
command:executedCommandRegistry{ id, result }
shortcut:firedShortcutEngine{ shortcut, commandId }
palette:openedCommandPalette
palette:closedCommandPalette
density:changedDensityController{ level, previous }
view:savedViewStateManager{ name, slot, shortcut, remaining, evicted }
view:loadedViewStateManager{ name }
view:evictedViewStateManager{ name, reason, maxViews }
selection:changedSelectionModel{ selected, count }
field:focusedFieldRegistry{ fieldId, label }
session:reviewedSessionTracker{ id, progress }
macro:playedMacroEngine{ name, steps }
history:undoHistoryManager{ commandId }
share:link-createdViewShare{ name, length, compressedSize }
share:publishedViewShare{ id, name, scope }
ctrlk:initializedCore{ version, options }

Keyboard Shortcuts

Default Shortcuts (configurable)

ShortcutActionExcel Equivalent
Ctrl+KOpen command palette
Ctrl+GJump to column / fieldF5 / Ctrl+G
Ctrl+DCycle density level
Ctrl+/Show shortcut reference
Ctrl+ZUndoCtrl+Z
Ctrl+YRedoCtrl+Y
Ctrl+Shift+SShare current view
Ctrl+1Ctrl+5Load saved view by slot
Alt+NNext unreviewed / empty
Alt+RToggle reviewed
Ctrl+→ / Ctrl+←Next / previous bookmarkCtrl+Arrow
F2Edit cellF2
F6Next focus zoneF6
EscapeClose overlay / cancelEscape
All shortcuts are customizable. Override any default with ctrlk.keys.bind('your-shortcut', 'command.id').

CtrlK — The first IOUX for enterprise web apps · ctrlk.dev
MIT License · Prabhu Raja