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.
Link to original
Keybinding Description ]|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
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]cand[cbindings.
Table text objects
Table text objects
Operate on table cells with standard Vim operators.
Link to original
Keybinding Description i|Inside table cell (content between pipes) a|Around table cell (content plus trailing pipe)
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 containsfoo \| bar.\\|(escaped backslash followed by pipe) is treated as a real boundary.
Table manipulation
Table manipulation
Manage table structure using the
<leader>tprefix.Link to original
Keybinding Description <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
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
nativemode, they work when the cursor is inside the table. Inrawmode, use Source mode or manual Markdown editing.
Table auto-formatting
Vim Motions includes built-in auto-formatting for tables:
- Manual realignment: Use
<Leader>tror:tablerealignto 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
nativemode: its table editor realigns the columns when it commits a cell edit. That is Obsidian’s behaviour, not this plugin’s — and becauseownedmode replaces Obsidian’s widget, automatic realignment does not happen there. Use:tablerealigninstead.
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 viaregisterEditorExtension(). 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.
| Configuration | Experience |
|---|---|
native + tablenav (default) | Full table-nav overlay with cell highlighting and structural commands |
native + notablenav | Native table editor with vim cell editing and cross-cell navigation |
raw | Raw markdown tables |
owned | The plugin’s own renderer, with Vim normal, visual and insert mode |
Owned table surface
Warning
Experimental, and not the default. Enable with
set tablewidget=ownedor Settings → Vim Motions → General → Table widget in live preview.
nativestays 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 arenative-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=ownedand 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.
Keybinding Description Any normal-mode command Routed 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/oand 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,.,zzand counts - Charwise and linewise visual mode, with the selection rendered inside the table
- Insert mode, including IME composition — a composition commits once and one
ureverses it - Leaving the table by
j/kat 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,:tablerealignand the rest of the:table*family — with their<leader>tbindings — operate on the table in this mode. The overlay’s one-key forms (o,O,dd,J,K,H,L,I,A) are specific tonative, where the overlay auto-activates inside a table; inownedthose 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=100disables horizontal scrolling inside a table innative.ownedno longer does: its monospace grid setswhite-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:
| Condition | Reason |
|---|---|
| Obsidian’s own Vim key bindings are on | Only the bundled engine has been measured against this surface |
| Mobile | Desktop only for now |
| Source mode or Reading view | Replacing table source in a mode meant to show source is a defect |
The setting is not owned | Opt-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
| Key | Action |
|---|---|
h / j / k / l | Navigate between cells |
Tab / Shift+Tab | Navigate to next / previous cell (wraps across rows) |
{count}j, {count}l, etc. | Navigate with count prefix (e.g., 3j) |
i / a / c / s / Enter | Start editing the active cell |
Escape | Exit table-nav and return to the main editor |
o | Add a row below |
O | Add a row above |
dd | Delete the current row |
dc | Delete the current column |
J | Move the current row down |
K | Move the current row up |
H | Move the current column to the left |
L | Move the current column to the right |
I | Add a column to the left |
A | Add 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:
| Key | In cell | At boundary |
|---|---|---|
l | Move right within cell | Move to next cell (same row) |
h | Move left within cell | Move to previous cell (same row) |
j | Move down within cell | Move to same column in next data row (skip separator) |
k | Move up within cell | Move to same column in previous data row (skip separator) |
w / W | Move to next word within cell | Move to next cell |
b / B | Move to previous word | Move to previous cell |
e / E | Move to end of word | Move to next cell |
ge / gE | Move to end of previous word | Move 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+Tabnavigate 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:
Escapein normal mode returns to table-nav.h/j/k/landw/b/ein 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:
Escapein normal mode stays in the cell;h/j/k/landw/b/ecross 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
Enterinside 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=ownedin your vimrc.set tablewidget=rawstill works but is deprecated — see below.
Ex commands
The following Ex commands are available for table manipulation:
| Command | Short | Description |
|---|---|---|
:tablerowbefore | :tablerowb | Add row above |
:tablerowafter | :tablerowa | Add row below |
:tablerowup | :tablerowu | Move row up |
:tablerowdown | :tablerowd | Move row down |
:tablerowdelete | :tablerowde | Delete row |
:tablecolbefore | :tablecolb | Add column left |
:tablecolafter | :tablecola | Add column right |
:tablecolleft | :tablecoll | Move column left |
:tablecolright | :tablecolr | Move column right |
:tablecoldelete | :tablecold | Delete column |
:tablealignleft | :tablealignl | Align column left |
:tablealigncenter | :tablealignc | Align column center |
:tablealignright | :tablealignr | Align column right |
:tableinsert | :tablei | Insert a new table |
:tablerealign | :tablerea | Realign the table |
See known-limitations > Tables for detailed technical limitations.