Introduction

Vim Motions provides comprehensive support for Markdown tables, including structural navigation, cell-level text objects, manipulation commands, and native table editor integration for Live Preview that preserves Vim’s editing power.

Cell navigation

Table navigation

Navigate Markdown table cells without leaving Vim mode.

KeybindingDescription
]| or ]cMove to the next table cell
[| or [cMove to the previous table cell
]rMove to same column in next row
[rMove to same column in previous row
Link to original

Table navigation commands allow you to move between cells horizontally and vertically.

  • Wrapping: Horizontal navigation (]|, [|, ]c, [c) wraps around to the next or previous row when reaching the end or beginning of a row.
  • Separator-skip: Vertical navigation (]r, [r) automatically skips over table separator rows (the |---| lines) to land on the same column in the next or previous content row.

Warning

On many non-US keyboard layouts, the pipe character (|) requires a modifier key (like AltGr) that may conflict with Vim’s key capture. If ]| or [| do not work on your keyboard, use the alternative ]c and [c bindings.

Table text objects

Table text objects

Operate on table cells with standard Vim operators.

KeybindingDescription
i|Inside table cell (content between pipes)
a|Around table cell (content plus trailing pipe)
Link to original

Table text objects allow you to operate on the content of individual cells using standard Vim operators:

  • di|: Delete the content of the current cell.
  • ci|: Change the content of the current cell (delete and enter insert mode).
  • yi|: Yank (copy) the content of the current cell.
  • vi|: Visually select the content of the current cell.

The a| variant includes the surrounding pipes and padding.

Tip

Escaped pipes (\|) inside table cells are treated as cell content, not boundaries. For example, | foo \| bar | baz | is a two-column table where the first cell contains foo \| bar. \\| (escaped backslash followed by pipe) is treated as a real boundary.

Table manipulation

Table manipulation

Manage table structure using the <leader>t prefix.

KeybindingDescription
<leader>tmInsert table
<leader>toAdd row below
<leader>tOAdd row above
<leader>tJMove row down
<leader>tKMove row up
<leader>tddDelete row
<leader>tiLAdd column to the right
<leader>tiHAdd column to the left
<leader>tLMove column right
<leader>tHMove column left
<leader>tdcDelete column
<leader>trRealign table columns
Link to original

A suite of manipulation commands is available under the <Leader>t prefix for structural changes to the table:

  • <Leader>to: Add a row below the current row.
  • <Leader>tO: Add a row above the current row.
  • <Leader>tj: Move the current row down.
  • <Leader>tk: Move the current row up.
  • <Leader>tdd: Delete the current row.
  • <Leader>tiL: Add a column to the right.
  • <Leader>tiH: Add a column to the left.
  • <Leader>tL: Move the current column to the right.
  • <Leader>tH: Move the current column to the left.
  • <Leader>tdc: Delete the current column.
  • <Leader>tr: Realign the entire table.

Note

These manipulation commands call Obsidian’s internal table commands. In native mode, they work when the cursor is inside the table. In raw mode, use Source mode or manual Markdown editing.

Table auto-formatting

Vim Motions includes built-in auto-formatting for tables:

  • Manual realignment: Use <Leader>tr or :tablerealign to realign a table’s columns at any time. In table-nav mode, = does the same.
  • Vim Motions never reformats a table on its own — not while you type, not when the cursor leaves the table. Realignment happens only when you ask for it, so the cursor stays where you expect it.
  • Obsidian does realign automatically, in native mode: its table editor realigns the columns when it commits a cell edit. That is Obsidian’s behaviour, not this plugin’s — and because owned mode replaces Obsidian’s widget, automatic realignment does not happen there. Use :tablerealign instead.

Table widget in Live Preview

Vim Motions integrates with Obsidian’s native table editor in Live Preview. Two rendering modes are available via set tablewidget:

  • native (default): Obsidian’s native table widget renders in Live Preview. Cell editors are native Obsidian editors with vim injected via registerEditorExtension(). The native editor handles wikilinks, pipe escaping (| → \|), cursor positioning, and <br> conversion automatically.
  • owned (experimental): the plugin renders the table itself, replacing Obsidian’s widget entirely. See tables > Owned table surface below.
  • raw (deprecated, will be removed): despite the name it does not show Markdown source. It hides Obsidian’s widget with CSS while Obsidian still replaces the table’s range, so the table renders as nothing at all. Use Source mode to edit a table’s source. No widget rendering. Useful for users who prefer source-style editing in Live Preview. The vim cursor remains fully visible in raw mode — cursor suppression only activates when a native table widget is visible.

In source mode, tables are always rendered as raw markdown regardless of the tablewidget setting. The cursor behaves normally — no cursor suppression occurs.

The tablenav setting (on by default) controls whether the table-nav overlay activates on top of the native editor. With tablenav off, the native table editor still provides full vim cell editing with cross-cell h/j/k/l navigation — just without the overlay UI.

ConfigurationExperience
native + tablenav (default)Full table-nav overlay with cell highlighting and structural commands
native + notablenavNative table editor with vim cell editing and cross-cell navigation
rawRaw markdown tables
ownedThe plugin’s own renderer, with Vim normal, visual and insert mode

Owned table surface

Warning

Experimental, and not the default. Enable with set tablewidget=owned or Settings → Vim Motions → General → Table widget in live preview.

native stays the default deliberately. The owned surface renders the table’s source in a monospace grid rather than as a formatted table, the nav overlay’s single-key structural commands are native-only, and several of Obsidian’s widget features have no equivalent — they are listed under What is not supported yet.

With owned, the plugin replaces Obsidian’s table decoration with its own and mounts a nested editor inside it when the cursor enters a table. The nested editor hosts the caret; the parent editor’s vim owns every command, so there is one vim state, one undo history and one document.

Owned table surface

Available when set tablewidget=owned and the cursor is inside a table in Live Preview. The plugin renders the table itself and a nested editor hosts the caret, while the parent editor’s vim owns every command — one vim state, one undo history, one document.

KeybindingDescription
Any normal-mode commandRouted to the parent’s vim (dd, D, u, ., zz, counts)
h / lMove by one character within the table
j / kMove one table row; at the first or last row, leaves the table
v / VCharwise / linewise visual mode, rendered inside the table
i / a / o and friendsEnter insert mode; typed text goes into the document
EscapeLeave insert or visual mode

Desktop, Live Preview and the bundled engine only. See tables > Owned table surface for what is not supported yet.

Link to original

What works

  • Normal-mode commands, including dd, D, u, ., zz and counts
  • Charwise and linewise visual mode, with the selection rendered inside the table
  • Insert mode, including IME composition — a composition commits once and one u reverses it
  • Leaving the table by j/k at the first or last row, preserving the desired column and any count; re-entering remounts

What is not supported yet

  • Structural commands work, but the nav overlay’s single keys do not. :tablerowafter, :tablecoldelete, :tablerealign and the rest of the :table* family — with their <leader>t bindings — operate on the table in this mode. The overlay’s one-key forms (o, O, dd, J, K, H, L, I, A) are specific to native, where the overlay auto-activates inside a table; in owned those keys keep their ordinary Vim meanings.
  • Under the Neovim backend the table is presentational. It renders, but the cell editor is inert and never focused so Neovim keeps the cursor, the text and the keys. Neovim’s own rendering — extmarks, flash labels, diagnostics, folds, signs — does not appear inside the table, and the active cell is not highlighted. See neovim-backend > Tables.
  • Neither renderer re-aligns a cell’s contents. Both the idle grid and the cursor’s editor show the table’s source characters exactly, with cells and delimiters marked up for styling and each column’s alignment carried as a class. A column marked ---: is therefore themeable but its text is not moved to the right, because the two renderers must agree glyph-for-glyph — otherwise the grid visibly shifts the moment the cursor enters the table. Neither is an HTML <table>, for the same reason.
  • The table-nav overlay (tablenav) is not reconciled with this mode.
  • Some Obsidian widget features are not reproduced: row and column buttons, the column context menu, column and row drag-to-reorder, sort by column, mouse multi-cell selection, and alignment-aware rendering. Four are supported: the cell context menu (a right-click reaches Obsidian’s editor menu), keyboard multi-cell selection (<C-v> renders a true rectangular selection, one range per row), click-to-place-cursor, and malformed tables, which are given defined behaviour — a short row yields fewer cells and a long one is truncated for layout, both leaving the document unchanged. Column resizing is not in that list — Obsidian’s widget does not offer it, so nothing is lost.
  • scrolloff=100 disables horizontal scrolling inside a table in native. owned no longer does: its monospace grid sets white-space: pre, so the nested editor does not wrap and still scrolls. Ordinary horizontal scrolling works in both — the nested editor follows the caret.
  • A snippet body containing a | or a newline damages the row. Snippets otherwise work in a cell in this mode — see snippets > Snippets in a table cell — but a literal pipe is inserted as-is and opens an extra column, and a newline cannot be represented in a row at all. Obsidian’s own table editor converts these to \| and <br>; this mode does not yet. Escape the pipe in the snippet definition as a workaround.
  • Only the active tabstop is marked. The remaining tabstops of an open snippet carry no highlight inside a cell, because that decoration is driven from the parent editor’s state and the cell renders its own. The active one is visible as the selection.

When it falls back

owned is skipped, with a one-time notice, when any of these hold. Each is a deliberate restriction rather than a missing feature:

ConditionReason
Obsidian’s own Vim key bindings are onOnly the bundled engine has been measured against this surface
MobileDesktop only for now
Source mode or Reading viewReplacing table source in a mode meant to show source is a defect
The setting is not ownedOpt-in

Table-nav mode

When the cursor enters a table in Live Preview with tablenav enabled, a navigation overlay activates. This mode allows you to navigate between cells and perform structural changes without entering the cell editor.

Keybindings

KeyAction
h / j / k / lNavigate between cells
Tab / Shift+TabNavigate to next / previous cell (wraps across rows)
{count}j, {count}l, etc.Navigate with count prefix (e.g., 3j)
i / a / c / s / EnterStart editing the active cell
EscapeExit table-nav and return to the main editor
oAdd a row below
OAdd a row above
ddDelete the current row
dcDelete the current column
JMove the current row down
KMove the current row up
HMove the current column to the left
LMove the current column to the right
IAdd a column to the left
AAdd a column to the right
=Realign the table
.Repeat the last structural command

Fork-only feature

Table-nav mode requires the bundled vim engine or the Neovim backend. If you are using Obsidian’s built-in vim mode, the plugin falls back to standard cell editing. Under the Neovim backend the overlay works normally: Obsidian’s keymap scope consumes its keys before they reach Neovim.

Tip

The native mode provides the best vim editing experience for tables. Obsidian’s native table widget handles rendering while vim is injected into cell editors. Structural commands let you add, delete, and move rows and columns without leaving the table. Notes with multiple tables are fully supported — each table is independently navigable.

Viewport scrolling

When navigating through a table taller or wider than the viewport, the editor scrolls both vertically and horizontally to keep the highlighted cell visible. This works even with tables that extend well beyond the screen.

Native mode vim navigation

In native mode, motions in normal mode cross cell boundaries automatically:

KeyIn cellAt boundary
lMove right within cellMove to next cell (same row)
hMove left within cellMove to previous cell (same row)
jMove down within cellMove to same column in next data row (skip separator)
kMove up within cellMove to same column in previous data row (skip separator)
w / WMove to next word within cellMove to next cell
b / BMove to previous wordMove to previous cell
e / EMove to end of wordMove to next cell
ge / gEMove to end of previous wordMove to previous cell
j at last row—Exit table downward
k at header—Exit table upward

Count prefixes work for cross-cell j/k motions: 3j crosses 3 rows.

Operator-pending (dj, yl) and visual mode motions are confined to the current cell — they do not trigger cross-cell navigation.

Vim modality in cell editors

Cell editors are Obsidian’s native editors with vim injected via registerEditorExtension(). Full Vim modality is supported: Normal, Insert, and Visual modes all work within a single table cell.

  • Tab / Shift+Tab navigate between cells, wrapping across rows (Tab at the last cell of a row moves to the first cell of the next row; Shift+Tab at the first cell wraps to the last cell of the previous row). When table-nav is enabled, Tab exits the cell editor and returns to table-nav on the destination cell.
  • When table-nav is enabled: Escape in normal mode returns to table-nav. h/j/k/l and w/b/e in normal mode move the cursor within the cell; only when the cursor reaches a cell boundary do they exit to table-nav and navigate to the adjacent cell. This lets you use standard vim cursor movement inside cells after pressing Escape from insert mode.
  • When table-nav is disabled: Escape in normal mode stays in the cell; h/j/k/l and w/b/e cross cell boundaries directly via motion overrides.
  • Register sharing: Vim registers are shared between cell editors and the main document.
  • Which-key: Which-key popups work in cell editors.

Cell editors use Live Preview

Cell editors use Obsidian’s Live Preview rendering. Markdown syntax like wikilink brackets ([[ ]]) and formatting marks are hidden during editing, but the underlying text is preserved.

Animated cursor in cells

When the animated cursor is enabled, table cell editors use the native vim cursor as the steady-state renderer. The canvas-based animated cursor cannot reliably render above table cell content due to CSS stacking contexts. Cross-cell navigation (h/j/k/l) snaps the cursor to the destination cell. Within a single cell, the native cursor renders normally.

Multi-line cell content

Pressing Enter inside a cell editor creates a line break. The native editor automatically handles <br> ↔ newline conversion so the table structure stays valid.

Table row text objects

In raw Markdown mode, you can operate on entire table rows using the ir and ar text objects:

  • ir: Selects the inner row content (everything between the first and last | pipes, excluding the pipes themselves).
  • ar: Selects the around row content (the entire line including the leading and trailing pipes).

These text objects are useful for quickly deleting, changing, or yanking whole rows while editing the Markdown source.

Info

You can configure the table widget mode in Settings → Vim Motions → Table widget in live preview, or via set tablewidget=native / set tablewidget=owned in your vimrc. set tablewidget=raw still works but is deprecated — see below.

Ex commands

The following Ex commands are available for table manipulation:

CommandShortDescription
:tablerowbefore:tablerowbAdd row above
:tablerowafter:tablerowaAdd row below
:tablerowup:tablerowuMove row up
:tablerowdown:tablerowdMove row down
:tablerowdelete:tablerowdeDelete row
:tablecolbefore:tablecolbAdd column left
:tablecolafter:tablecolaAdd column right
:tablecolleft:tablecollMove column left
:tablecolright:tablecolrMove column right
:tablecoldelete:tablecoldDelete column
:tablealignleft:tablealignlAlign column left
:tablealigncenter:tablealigncAlign column center
:tablealignright:tablealignrAlign column right
:tableinsert:tableiInsert a new table
:tablerealign:tablereaRealign the table

See known-limitations > Tables for detailed technical limitations.