# Excel keyboard shortcuts

The muscle memory a spreadsheet user arrives with: Ctrl+Arrow jumps to the edge of the data region (a run-boundary search, so it hops gaps rather than running to the end), Ctrl+Shift+Arrow extends there, Ctrl+A takes the current region then the sheet, Ctrl+Space / Shift+Space take the column or row, Ctrl+D / Ctrl+R fill, Ctrl+; stamps the date. One enableSheet() call. Ctrl+Z undoes a whole fill in one press, not one per cell.

Requires `@svgrid/enterprise` (and `@svgrid/grid`). Category: Spreadsheet. Live demo: https://svgrid.com/demos/452-excel-shortcuts/

Install: `npm i @svgrid/grid @svgrid/enterprise` (Svelte 5).

## About this example

Excel keyboard shortcuts on a Svelte 5 SvGrid with one enableSheet() call: Ctrl+Arrow jumps to the edge of the data region with a run-boundary search that hops gaps instead of running to the last row, Ctrl+Shift+Arrow extends the selection there, Ctrl+A takes the current region and then the sheet, Ctrl+Space and Shift+Space take the column or row, Ctrl+D and Ctrl+R fill down and right, Ctrl+; stamps the date. A whole fill is one undo step. Enterprise, in @svgrid/enterprise.

The muscle memory a spreadsheet user arrives with, on a plain <SvGrid>. One call turns it on:

```text
import { enableSheet } from '@svgrid/enterprise'
enableSheet()
```

Navigation Ctrl+Arrow        jump to the edge of the data region. Not "move a long way" - it is a run-boundary search, so from a filled cell it runs to the last filled cell before the next blank, and from the edge of a block it hops the gap to the next block. Leave a gap in a column and try it both ways. Ctrl+Shift+Arrow  the same jump, extending the selection. Ctrl+A            select the current region; press it again to take the whole sheet. Ctrl+Space        select the column.   Shift+Space  select the row.

Fill and entry Ctrl+D            fill down. With a range selected it copies the top row into the rest; with one cell it pulls from the cell above, which is what makes it useful while typing. Ctrl+R            fill right, same two behaviours. Ctrl+;            stamp today's date.  Ctrl+Shift+;  stamp the time. Ctrl+'            copy the cell above VERBATIM. When the formula engine lands this is how you get an unshifted copy to edit. Ctrl+Z            one press per action, not per cell - a fill down 30 rows walks back in one.

The grid still owns Ctrl+C / X / V, Ctrl+F and the arrow keys. A command only takes a key when it has something to do: Ctrl+D on the top row with nothing above it declines, and the key falls through.

## Imports, features and API used

- Imports: `@svgrid/grid`, `@svgrid/enterprise`
- Table features registered: `rowSortingFeature`
- Columns: `task` (Task), `owner` (Owner), `q1` (Q1), `q2` (Q2), `q3` (Q3), `q4` (Q4), `due` (Due)

## Frequently asked questions

### How do the shortcuts get onto the grid?

enableSheet() registers them once on the grid's shortcut registry; every SvGrid on the page then answers Ctrl+Arrow, Ctrl+Shift+Arrow, Ctrl+A, Ctrl+Space, Shift+Space, Ctrl+D, Ctrl+R and Ctrl+; the way Excel does.

### What does Ctrl+Arrow do at a gap?

It stops at the edge of the current run of filled cells, or at the next filled cell when it starts on a blank one, so it hops across gaps rather than running to the end of the sheet.

### Is a fill one undo step?

Yes. Ctrl+D and Ctrl+R write through the grid's command context in one batch, so Ctrl+Z takes the whole fill back at once, not one cell per press.

## Related documentation

- [Excel keyboard shortcuts](https://svgrid.com/docs/help/cells/keyboard-shortcuts.md)

## Related articles

- [Paste from Excel into a Svelte Data Grid](https://svgrid.com/blog/paste-from-excel/) - How to wire up clipboard paste so users can drop a copied Excel or Google Sheets block directly into SvGrid - TSV parsing, type coercion, validation, and row growth all covered.
- [How We Built Excel-Style Filters](https://svgrid.com/blog/how-we-built-excel-style-filters/) - How SvGrid implements per-column filter menus, type-aware operators, and a shared filter model that works identically for in-memory and server-side data.
- [A Fill Handle (Drag to Fill) in SvGrid](https://svgrid.com/blog/fill-handle-drag-to-fill/) - Build a working spreadsheet-style fill handle on top of SvGrid's cell selection and editing - pointer tracking, range highlighting, series fill, and undo/redo integration all covered.

## Source code (452-excel-shortcuts.svelte)

```svelte
<script lang="ts">
  /**
   * 452. Excel keyboard shortcuts
   * ------------------------------
   * The muscle memory a spreadsheet user arrives with, on a plain <SvGrid>.
   * One call turns it on:
   *
   *   import { enableSheet } from '@svgrid/enterprise'
   *   enableSheet()
   *
   * Navigation
   *   Ctrl+Arrow        jump to the edge of the data region. Not "move a long
   *                     way" - it is a run-boundary search, so from a filled
   *                     cell it runs to the last filled cell before the next
   *                     blank, and from the edge of a block it hops the gap to
   *                     the next block. Leave a gap in a column and try it
   *                     both ways.
   *   Ctrl+Shift+Arrow  the same jump, extending the selection.
   *   Ctrl+A            select the current region; press it again to take the
   *                     whole sheet.
   *   Ctrl+Space        select the column.   Shift+Space  select the row.
   *
   * Fill and entry
   *   Ctrl+D            fill down. With a range selected it copies the top row
   *                     into the rest; with one cell it pulls from the cell
   *                     above, which is what makes it useful while typing.
   *   Ctrl+R            fill right, same two behaviours.
   *   Ctrl+;            stamp today's date.  Ctrl+Shift+;  stamp the time.
   *   Ctrl+'            copy the cell above VERBATIM. When the formula engine
   *                     lands this is how you get an unshifted copy to edit.
   *   Ctrl+Z            one press per action, not per cell - a fill down 30
   *                     rows walks back in one.
   *
   * The grid still owns Ctrl+C / X / V, Ctrl+F and the arrow keys. A command
   * only takes a key when it has something to do: Ctrl+D on the top row with
   * nothing above it declines, and the key falls through.
   */
  import {
    SvGrid,
    tableFeatures,
    rowSortingFeature,
    type GridColumns,
  } from '@svgrid/grid'
  import { enableSheet } from '@svgrid/enterprise'

  enableSheet()

  type Row = {
    id: number
    task: string
    owner: string
    q1: number | null
    q2: number | null
    q3: number | null
    q4: number | null
    due: string
  }

  // Deliberately gappy. A dense block would make Ctrl+Arrow look like
  // Ctrl+Home, and the run-boundary behaviour is the whole point.
  const SEED: Array<[string, string, Array<number | null>]> = [
    ['Design review', 'Ada', [12, 14, null, null]],
    ['Prototype', 'Ada', [8, 11, 9, null]],
    ['User testing', 'Lin', [null, 6, 7, 5]],
    ['', '', [null, null, null, null]],
    ['Rollout plan', 'Kai', [4, 4, null, null]],
    ['Migration', 'Kai', [9, 12, 15, null]],
    ['', '', [null, null, null, null]],
    ['Docs', 'Lin', [3, null, null, null]],
    ['Training', 'Ada', [null, null, 6, 8]],
  ]

  const start = (): Row[] =>
    SEED.map(([task, owner, q], i) => ({
      id: i + 1,
      task,
      owner,
      q1: q[0] ?? null,
      q2: q[1] ?? null,
      q3: q[2] ?? null,
      q4: q[3] ?? null,
      due: '',
    }))

  let rows = $state<Row[]>(start())

  const features = tableFeatures({ rowSortingFeature })

  const columns: GridColumns<Row> = [
    { id: 'task', field: 'task', header: 'Task', width: 180 },
    { id: 'owner', field: 'owner', header: 'Owner', width: 100 },
    { id: 'q1', field: 'q1', header: 'Q1', width: 80, editorType: 'number' },
    { id: 'q2', field: 'q2', header: 'Q2', width: 80, editorType: 'number' },
    { id: 'q3', field: 'q3', header: 'Q3', width: 80, editorType: 'number' },
    { id: 'q4', field: 'q4', header: 'Q4', width: 80, editorType: 'number' },
    { id: 'due', field: 'due', header: 'Due', width: 130 },
  ]

  const CHEATSHEET: Array<[string, string]> = [
    ['Ctrl + Arrow', 'Jump to the edge of the data region'],
    ['Ctrl + Shift + Arrow', 'Extend the selection to that edge'],
    ['Ctrl + A', 'Select the current region, then the sheet'],
    ['Ctrl + Space', 'Select the column'],
    ['Shift + Space', 'Select the row'],
    ['Ctrl + D', 'Fill down'],
    ['Ctrl + R', 'Fill right'],
    ['Ctrl + ;', "Stamp today's date"],
    ['Ctrl + Shift + ;', 'Stamp the current time'],
    ["Ctrl + '", 'Copy the cell above, unchanged'],
    ['Ctrl + Z', 'Undo the whole action, not one cell'],
  ]
</script>

<section class="wrap">
  <SvGrid
    data={rows}
    {columns}
    {features}
    selectionMode="cell"
    enableCellSelection={true}
    enableInlineEditing={true}
    statusBar={true}
    filterMode="none"
    containerHeight={360}
  />

  <aside class="keys">
    <h3>Try these</h3>
    <dl>
      {#each CHEATSHEET as [combo, what] (combo)}
        <div class="row">
          <dt><kbd>{combo}</kbd></dt>
          <dd>{what}</dd>
        </div>
      {/each}
    </dl>
    <div class="foot">
      <p class="note">
        Click a cell in Q1 first. The blank rows are there on purpose:
        Ctrl+Down runs to the bottom of a block, then hops the gap on the
        next press.
      </p>
      <button type="button" onclick={() => (rows = start())}>
        Reset the data
      </button>
    </div>
  </aside>
</section>

<style>
  .wrap {
    display: flex;
    flex-direction: column;
    gap: 14px;
  }
  /* The grid sizes itself from its column widths, so it is left to take the
     full row rather than sharing one with the cheat sheet: a side-by-side
     flex row makes the table overflow its track and paint over the aside. */
  .keys {
    font-size: 13px;
  }
  h3 {
    margin: 0 0 8px;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--sg-muted, #64748b);
  }
  dl {
    margin: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
    gap: 0 20px;
  }
  .row {
    display: flex;
    gap: 10px;
    align-items: baseline;
    padding: 4px 0;
    border-bottom: 1px solid var(--sg-border, #e2e8f0);
  }
  dt {
    flex: 0 0 130px;
  }
  dd {
    margin: 0;
    color: var(--sg-muted, #64748b);
  }
  kbd {
    font-family: ui-monospace, Menlo, monospace;
    font-size: 11px;
    white-space: nowrap;
    border: 1px solid var(--sg-border, #cbd5e1);
    border-radius: 3px;
    padding: 1px 5px;
  }
  .foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    margin-top: 10px;
  }
  .note {
    margin: 0;
    flex: 1 1 320px;
    color: var(--sg-muted, #64748b);
    line-height: 1.5;
  }
  button {
    flex: 0 0 auto;
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    padding: 6px 12px;
    border-radius: 7px;
    border: 1px solid var(--sg-border, #cbd5e1);
    background: var(--sg-bg, #fff);
    color: var(--sg-fg, #0f172a);
    cursor: pointer;
  }
  button:hover {
    background: var(--sg-row-hover-bg, #f1f5f9);
  }
</style>
```

[View this example on GitHub](https://github.com/sv-grid/sv-grid/blob/main/examples/src/demos/452-excel-shortcuts.svelte)
