---
title: "Keyboard Interaction"
framework: vue
version: "36.1.0"
---

# Keyboard Interaction

The grid responds to keyboard interactions from the user as well as emitting events when key presses happen on the grid cells. Below shows all the keyboards interactions that can be done with the grid.

## Navigation

Use the arrow keys (`←` `↑` `→` `↓`) to move focus up, down, left and right. If the focused cell is already on the boundary for that position (e.g. if on the first column and the left key is pressed) then the key press has no effect. Use `^ Ctrl`+`←` to move to the start of the line, and `^ Ctrl`+`→` to move to the end.

If a cell on the first grid row is focused and you press `↑`, the focus will be moved into the grid header. The header navigation focus navigation works the same as the grid's: arrows will move up/down/left/right, `⇥ Tab` will move the focus horizontally until the last header cell and then move on to the next row.

Use `Page Up` and `Page Down` to move the scroll up and down by one page. Use `Home` and `End` to go to the first and last rows.

> **Note**
>
> When a header cell is focused, commands like `Page Up`, `Page Down`, `Home`, `End`, `^ Ctrl`+`←`/`→` will not work as they do when a grid cell is focused.

## Groups

If on a group element, hitting the `↵ Enter` key will expand or collapse the group.

## Editing

Pressing the `F2` or `↵ Enter` key on a cell will put the cell into edit mode, if editing is allowed on the cell. This will work for the default cell editor.

Pressing `^ Ctrl`+`↵ Enter` keys when selecting a cell range and editing one of the cells, sets the new value of the edited cell to all the editable cells in the selected range

## Selection

Pressing the `␣ Space` key when focusing a cell will select the cell's row, or deselect the row if already selected. If multi-select is enabled, this selection change will not remove any previous selections.

## Suppress Focus

If you want keyboard navigation turned off, there are two properties that need to be turned off.

### Suppress Cell Focus

Set `suppressCellFocus=true` in the gridOptions, and Grid Cell Focus will be disabled.

### Suppress Header Focus

Set `suppressHeaderFocus=true` in the gridOptions, and Grid Header Focus will be disabled.

## Column Header Navigation

The grid header supports full keyboard navigation, however the behaviour may differ based on the type of Column Header that is currently focused.

Column Headers can be:

- Moved by pressing `⇧ Shift` + `←` / `→`.
- Resized by pressing `⌥ Alt` + `←` / `→`.

### Column Group Headers

While navigating Column Groups Headers, you can do the following:

- If the current Column Group is expandable, pressing `↵ Enter` will toggle the expanded state of the group.
- When [Column Cell Selection](https://www.ag-grid.com/vue-data-grid/cell-selection/#selecting-cells-via-column-headers) is enabled:
  - If the current Column Group is expandable, pressing `⌥ Alt`+`↵ Enter` will toggle the expanded state of the group.
  - Press `↵ Enter` to select all visible cells in child columns of the focused column group.
  - Press `^ Ctrl`+`↵ Enter` to select all visible cells in child columns of the focused column group without clearing existing cell ranges.
  - Press `⇧ Shift`+`↵ Enter` to select all cells in visible child columns between the previously selected column group and the focused column group.
  - Press `^ Ctrl`+`⇧ Shift`+`↵ Enter` to select all cells in visible child columns between the previously selected column group and the focused column group, without clearing existing cell ranges.

### Normal Column Headers

Regular Column Headers may have selection checkboxes, sorting functions and menus, so to access all these functions while focusing a Column Header, you can do the following:

- Press `␣ Space` to toggle the Column Header checkbox selection.
- Press `↵ Enter` to toggle the sorting state of that column.
- Press `⇧ Shift`+`↵ Enter` to toggle multi-sort for that column.
- Press `⌥ Alt`+`↓` to open the menu for the focused Column Header.
- Press `^ Ctrl`+`↵ Enter` to either open the filter for the focused Column Header (if `columnMenu = 'new'` - default behaviour) or open the menu for the focused Column Header (if `columnMenu = 'legacy'`).
- When a menu is open, simply press `⎋ Esc` to close it and the focus will return to the Column Header.
- When [Column Cell Selection](https://www.ag-grid.com/vue-data-grid/cell-selection/#selecting-cells-via-column-headers) is enabled:
  - Press `↵ Enter` to select all visible cells in the focused column.
  - Press `^ Ctrl`+`↵ Enter` to select all visible cells in the focused column without clearing existing cell ranges.
  - Press `⇧ Shift`+`↵ Enter` to select all cells in visible columns between the previously selected column and the focused column.
  - Press `^ Ctrl`+`⇧ Shift`+`↵ Enter` to select all cells in visible columns between the previously selected column and the focused column without clearing existing cell ranges.
  - Press `⌥ Alt`+`↵ Enter` to toggle the sorting state of that column.
  - Press `⌥ Alt`+`⇧ Shift`+`↵ Enter` to toggle multi-sort for that column.

### Floating Filter Headers

While navigating the Floating Filter Columns Headers with the keyboard, pressing `←` `→` will move focus from one Column Header to the next. If you wish to navigate within the Floating Filter Cell, press `↵ Enter` to focus the first enabled element within the current Floating Filter Cell, and press `⎋ Esc` to return focus to the Floating Filter Column Header.

## Example

The example below has grouped headers, headers and floating filters to demonstrate the features mentioned above:

#### Keyboard Navigation

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  NumberEditorModule,
  NumberFilterModule,
  RowSelectionModule,
  RowSelectionOptions,
  SideBarDef,
  TextEditorModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ClipboardModule,
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  FiltersToolPanelModule,
  PivotModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  NumberEditorModule,
  TextEditorModule,
  RowSelectionModule,
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  FiltersToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  SetFilterModule,
  NumberFilterModule,
  ClipboardModule,
  PivotModule,
]);

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :rowSelection="rowSelection"
      :defaultColDef="defaultColDef"
      :sideBar="sideBar"
      :rowData="rowData"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<IOlympicData> | null>(null);
    const columnDefs = ref<(ColDef | ColGroupDef)[]>([
      {
        headerName: "Participant",
        children: [
          { field: "athlete", minWidth: 170 },
          { field: "country", minWidth: 150 },
        ],
      },
      { field: "sport" },
      {
        headerName: "Medals",
        children: [
          {
            field: "total",
            columnGroupShow: "closed",
            filter: "agNumberColumnFilter",
            width: 120,
            flex: 0,
          },
          {
            field: "gold",
            columnGroupShow: "open",
            filter: "agNumberColumnFilter",
            width: 100,
            flex: 0,
          },
          {
            field: "silver",
            columnGroupShow: "open",
            filter: "agNumberColumnFilter",
            width: 100,
            flex: 0,
          },
          {
            field: "bronze",
            columnGroupShow: "open",
            filter: "agNumberColumnFilter",
            width: 100,
            flex: 0,
          },
        ],
      },
      { field: "year", filter: "agNumberColumnFilter" },
    ]);
    const rowSelection = ref<RowSelectionOptions | "single" | "multiple">({
      mode: "multiRow",
    });
    const defaultColDef = ref<ColDef>({
      editable: true,
      minWidth: 100,
      filter: true,
      floatingFilter: true,
      flex: 1,
    });
    const sideBar = ref<SideBarDef | string | string[] | boolean | null>({
      toolPanels: ["columns", "filters"],
      defaultToolPanel: "",
    });
    const rowData = ref<IOlympicData[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => (rowData.value = data);

      fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      columnDefs,
      rowSelection,
      defaultColDef,
      sideBar,
      rowData,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Keyboard Navigation](https://www.ag-grid.com/examples/keyboard-navigation/grid-keyboard-navigation/vue3)

## Custom Navigation

Most people will be happy with the default navigation the grid does when you use the arrow keys and the `⇥ Tab` key. Some people will want to override this (e.g. you may want the `⇥ Tab` key to navigate to the cell below, not the cell to the right). To facilitate this, the grid offers five methods: `navigateToNextCell`, `tabToNextCell`, `navigateToNextHeader`, `tabToNextHeader` and `tabToNextGridContainer`.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `focusGridInnerElement` | `FocusGridInnerElement` |  |  | Allows overriding the element that will be focused when the grid receives focus from outside elements (tabbing into the grid). |
| `navigateToNextHeader` | `NavigateToNextHeader` |  |  | Allows overriding the default behaviour for when user hits navigation (arrow) key when a header is focused. Return the next Header position to navigate to or `null` to stay on current header. |
| `tabToNextHeader` | `TabToNextHeader` |  |  | Allows overriding the default behaviour for when user hits `Tab` key when a header is focused. Return the next header position to navigate to, `true` to stay on the current header, or `false` to let the browser handle the tab behaviour. |
| `navigateToNextCell` | `NavigateToNextCell` |  |  | Allows overriding the default behaviour for when user hits navigation (arrow) key when a cell is focused. Return the next Cell position to navigate to or `null` to stay on current cell. |
| `tabToNextCell` | `TabToNextCell` |  |  | Allows overriding the default behaviour for when user hits `Tab` key when a cell is focused. Return the next cell position to navigate to, `true` to stay on the current cell, or `false` to let the browser handle the tab behaviour. |
| `tabToNextGridContainer` | `TabToNextGridContainer` |  |  | Allows overriding the default behaviour when tabbing between core grid containers. Return a container name, a cell position, or a header position to focus that target, `true` to stay on the current focus, `false` to let the browser handle tab behaviour, or `undefined` to use the grid's default behaviour. |

> **Note**
>
> The `navigateToNextCell` and `tabToNextCell` are only called while navigating across grid cells, while `navigateToNextHeader` and `tabToNextHeader` are only called while navigating across grid headers. The `tabToNextGridContainer` callback is called when tab navigation moves between core grid containers. If you need to navigate from one container to another, pass `rowIndex: -1` in `CellPosition` or `headerRowIndex: -1` in `HeaderPosition`.

## Example Custom Cell Navigation

The example below shows how to use `navigateToNextCell`, `tabToNextCell`, `navigateToNextHeader` and `tabToNextHeader` in practice.

Note the following:

- `navigateToNextCell` swaps the up and down arrow keys.
- `tabToNextCell` uses tabbing to go up and down rather than right and left.
- `navigateToNextHeader` swaps the up and down arrow keys.
- `tabToNextHeader` uses tabbing to go up and down rather than right and left.
- When a cell in the first grid row is focused, pressing the down arrow will navigate to the header by passing `rowIndex: -1`.
- When a header cell in the last header row is focused, pressing the up arrow will navigate to the first grid row by passing `headerRowIndex: -1`.
- Tabbing/Shift tabbing will move the focus until the first header or the last grid row, but focus will not leave the grid.

#### Custom Keyboard Navigation

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  CellPosition,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  Column,
  ColumnGroup,
  GridApi,
  GridOptions,
  GridReadyEvent,
  HeaderPosition,
  ModuleRegistry,
  NavigateToNextCell,
  NavigateToNextCellParams,
  NavigateToNextHeader,
  NavigateToNextHeaderParams,
  NumberEditorModule,
  NumberFilterModule,
  RowApiModule,
  TabToNextCell,
  TabToNextCellParams,
  TabToNextHeader,
  TabToNextHeaderParams,
  TextEditorModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowApiModule,
  NumberEditorModule,
  TextEditorModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
]);

// define some handy keycode constants
const KEY_LEFT = "ArrowLeft";

const KEY_UP = "ArrowUp";

const KEY_RIGHT = "ArrowRight";

const KEY_DOWN = "ArrowDown";

function moveHeaderFocusUpDown(
  previousHeader: HeaderPosition,
  headerRowCount: number,
  isUp: boolean,
): HeaderPosition {
  const previousColumn = previousHeader.column;
  const isSpanHeaderHeight =
    !!(previousColumn as Column).isSpanHeaderHeight &&
    (previousColumn as Column).isSpanHeaderHeight();
  const lastRowIndex = previousHeader.headerRowIndex;
  let nextRowIndex = isUp ? lastRowIndex - 1 : lastRowIndex + 1;
  let nextColumn;
  if (nextRowIndex === -1) {
    return previousHeader;
  }
  if (nextRowIndex === headerRowCount) {
    nextRowIndex = -1;
  }
  let parentColumn = previousColumn.getParent();
  if (isUp) {
    if (isSpanHeaderHeight) {
      while (parentColumn && parentColumn.isPadding()) {
        parentColumn = parentColumn.getParent();
      }
    }
    if (!parentColumn) {
      return previousHeader;
    }
    nextColumn = parentColumn;
  } else {
    const children =
      ((previousColumn as ColumnGroup).getChildren &&
        (previousColumn as ColumnGroup).getChildren()) ||
      [];
    nextColumn = children.length > 0 ? children[0] : previousColumn;
  }
  return {
    headerRowIndex: nextRowIndex,
    column: nextColumn as Column,
  };
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :defaultColDef="defaultColDef"
      :navigateToNextHeader="navigateToNextHeader"
      :tabToNextHeader="tabToNextHeader"
      :tabToNextCell="tabToNextCell"
      :navigateToNextCell="navigateToNextCell"
      :rowData="rowData"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<IOlympicData> | null>(null);
    const columnDefs = ref<(ColDef | ColGroupDef)[]>([
      {
        headerName: "Athlete",
        children: [
          { field: "athlete", headerName: "Name", minWidth: 170 },
          { field: "age" },
          { field: "country" },
        ],
      },
      { field: "year" },
      { field: "sport" },
      {
        headerName: "Medals",
        children: [
          { field: "gold" },
          { field: "silver" },
          { field: "bronze" },
          { field: "total" },
        ],
      },
    ]);
    const defaultColDef = ref<ColDef>({
      editable: true,
      flex: 1,
      minWidth: 100,
      filter: true,
    });
    const rowData = ref<IOlympicData[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => (rowData.value = data);

      fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };
    const navigateToNextHeader: (
      params: NavigateToNextHeaderParams,
    ) => HeaderPosition | null = (params: NavigateToNextHeaderParams) => {
      const nextHeader = params.nextHeaderPosition;
      if (params.key !== "ArrowDown" && params.key !== "ArrowUp") {
        return nextHeader;
      }
      const processedNextHeader = moveHeaderFocusUpDown(
        params.previousHeaderPosition!,
        params.headerRowCount,
        params.key === "ArrowDown",
      );
      return processedNextHeader;
    };
    const tabToNextHeader: (
      params: TabToNextHeaderParams,
    ) => HeaderPosition | null = (params: TabToNextHeaderParams) => {
      return moveHeaderFocusUpDown(
        params.previousHeaderPosition!,
        params.headerRowCount,
        params.backwards,
      );
    };
    const tabToNextCell: (
      params: TabToNextCellParams,
    ) => CellPosition | null = (params: TabToNextCellParams) => {
      const previousCell = params.previousCellPosition;
      const renderedRowCount = params.api!.getDisplayedRowCount();
      const lastRowIndex = previousCell.rowIndex;
      let nextRowIndex = params.backwards ? lastRowIndex - 1 : lastRowIndex + 1;
      if (nextRowIndex < 0) {
        nextRowIndex = -1;
      }
      if (nextRowIndex >= renderedRowCount) {
        nextRowIndex = renderedRowCount - 1;
      }
      const result = {
        rowIndex: nextRowIndex,
        column: previousCell.column,
        rowPinned: previousCell.rowPinned,
      };
      return result;
    };
    const navigateToNextCell: (
      params: NavigateToNextCellParams,
    ) => CellPosition | null = (params: NavigateToNextCellParams) => {
      const previousCell = params.previousCellPosition,
        suggestedNextCell = params.nextCellPosition;
      let nextRowIndex, renderedRowCount;
      switch (params.key) {
        case KEY_DOWN:
          // return the cell above
          nextRowIndex = previousCell.rowIndex - 1;
          if (nextRowIndex < -1) {
            return null;
          } // returning null means don't navigate
          return {
            rowIndex: nextRowIndex,
            column: previousCell.column,
            rowPinned: previousCell.rowPinned,
          };
        case KEY_UP:
          // return the cell below
          nextRowIndex = previousCell.rowIndex + 1;
          renderedRowCount = params.api!.getDisplayedRowCount();
          if (nextRowIndex >= renderedRowCount) {
            return null;
          } // returning null means don't navigate
          return {
            rowIndex: nextRowIndex,
            column: previousCell.column,
            rowPinned: previousCell.rowPinned,
          };
        case KEY_LEFT:
        case KEY_RIGHT:
          return suggestedNextCell;
        default:
          throw Error(
            "this will never happen, navigation is always one of the 4 keys above",
          );
      }
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      navigateToNextHeader,
      tabToNextHeader,
      tabToNextCell,
      navigateToNextCell,
      rowData,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Custom Keyboard Navigation](https://www.ag-grid.com/examples/keyboard-navigation/custom-keyboard-navigation/vue3)

## Custom Master Detail Navigation

[Master Detail Grids](https://www.ag-grid.com/vue-data-grid/master-detail/) can contain [Custom Details](https://www.ag-grid.com/vue-data-grid/master-detail-custom-detail/) that have their own renderer and hence will need to implement its own keyboard navigation. An example of this can be seen in the [Custom Details Keyboard Navigation Example](https://www.ag-grid.com/vue-data-grid/master-detail-custom-detail/#keyboard-navigation).

## Tabbing into the Grid

In applications where the grid is embedded into a larger page, by default, when tabbing into the grid, the first column header will be focused.

You could override this behaviour to focus the first grid cell, if that is a preferred scenario using a combination of DOM event listeners and Grid API calls shown in the following code snippet:

```ts
// obtain reference to input element
const myInput = document.getElementById("my-input");

// intercept key strokes within input element
myInput.addEventListener("keydown", event => {
    // ignore non tab key strokes
    if (event.key !== 'Tab') return;

    // prevents tabbing into the url section
    event.preventDefault();

    // scrolls to the first row
    gridApi.ensureIndexVisible(0);

    // scrolls to the first column
    const firstCol = gridApi.getAllDisplayedColumns()[0];
    gridApi.ensureColumnVisible(firstCol);

    // sets focus into the first grid cell
    gridApi.setFocusedCell(0, firstCol);

}, true);
```

### Tabbing into the Grid

In the following example there are two input box provided to test tabbing into the grid. Notice the following:

- Tabbing out of the input above the grid will focus the first grid header.
- When the first cell is out of view due to either scrolling down (rows) or across (columns), the grid will scroll back to the left to display the first column.
- Tabbing out of the input below the grid with shift pressed will focus the last cell of the grid.
- When the last column is out of view due to horizontal scroll, shift tabbing into the grid will cause the grid to scroll to focus the last cell.

#### Tabbing into the Grid

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  NumberEditorModule,
  NumberFilterModule,
  TextEditorModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  NumberEditorModule,
  TextEditorModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
]);

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="test-container">
      <div>
        <div class="form-container">
          <label>
            Input Above
            <input>
            </label>
          </div>
        </div>
        <ag-grid-vue
          style="width: 100%; height: 100%;"
          @grid-ready="onGridReady"
          :columnDefs="columnDefs"
          :defaultColDef="defaultColDef"
          :rowData="rowData"></ag-grid-vue>
          <div class="form-container">
            <label>
              Input Below
              <input>
              </label>
            </div>
          </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<IOlympicData> | null>(null);
    const columnDefs = ref<ColDef[]>([
      { headerName: "#", colId: "rowNum", valueGetter: "node.id" },
      { field: "athlete", minWidth: 170 },
      { field: "age" },
      { field: "country" },
      { field: "year" },
      { field: "date" },
      { field: "sport" },
      { field: "gold" },
      { field: "silver" },
      { field: "bronze" },
      { field: "total" },
    ]);
    const defaultColDef = ref<ColDef>({
      editable: true,
      flex: 1,
      minWidth: 100,
      filter: true,
    });
    const rowData = ref<IOlympicData[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => (rowData.value = data);

      fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      rowData,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Tabbing into the Grid](https://www.ag-grid.com/examples/keyboard-navigation/tabbing-into-grid/vue3)

### Custom Tabbing Between Grid Containers

Use `tabToNextGridContainer` to override focus behaviour when tabbing between grid containers such as the grid body, pagination toolbar and external elements.

In the following example:

- Tabbing out of the last grid cell is redirected to the pagination toolbar by returning `'pagination'`.
- Shift tabbing from the pagination toolbar back into the grid restores the last focused cell by returning a `CellPosition`.
- Tabbing forward from pagination returns `false`, allowing browser default behaviour to move focus outside the grid.
- All other transitions return `undefined`, which keeps the grid's default behaviour.

#### Custom Tab to Next Grid Container

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  CellPosition,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  PaginationModule,
  TabToNextGridContainer,
  TabToNextGridContainerParams,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  PaginationModule,
  TextFilterModule,
  ClientSideRowModelModule,
]);

let lastFocusedCell: CellPosition | null = null;

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="test-container">
      <div>
        <div class="form-container">
          <label>
            Input Above
            <input type="text">
            </label>
          </div>
        </div>
        <ag-grid-vue
          style="width: 100%; height: 100%;"
          @grid-ready="onGridReady"
          :columnDefs="columnDefs"
          :tabToNextGridContainer="tabToNextGridContainer"
          :defaultColDef="defaultColDef"
          :pagination="true"
          :rowData="rowData"
          @cell-focused="onCellFocused"></ag-grid-vue>
          <div class="form-container">
            <label>
              Input Below
              <input type="text">
              </label>
            </div>
          </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<IOlympicData> | null>(null);
    const columnDefs = ref<ColDef[]>([
      {
        headerName: "#",
        colId: "rowNum",
        valueGetter: "node.id",
        maxWidth: 90,
      },
      { field: "athlete", minWidth: 170 },
      { field: "age" },
      { field: "country" },
      { field: "year" },
      { field: "date" },
      { field: "sport" },
      { field: "gold" },
      { field: "silver" },
      { field: "bronze" },
      { field: "total" },
    ]);
    const tabToNextGridContainer = ref<TabToNextGridContainer>(
      (params: TabToNextGridContainerParams<IOlympicData>) => {
        const { backwards, previousContainer, nextContainer, defaultTarget } =
          params;
        // route tabbing out of the last grid cell into pagination controls first.
        if (
          !backwards &&
          previousContainer === "gridBody" &&
          nextContainer === "external"
        ) {
          return "pagination";
        }
        // restore last focused cell when shift-tabbing from pagination back into the grid.
        if (
          backwards &&
          previousContainer === "pagination" &&
          nextContainer === "gridBody"
        ) {
          const target = lastFocusedCell ?? defaultTarget;
          return target == null ? undefined : target;
        }
        // from pagination forwards, allow browser default focus flow to leave the grid.
        if (
          !backwards &&
          previousContainer === "pagination" &&
          nextContainer === "external"
        ) {
          return false;
        }
        // For everything else, keep grid defaults.
        return undefined;
      },
    );
    const defaultColDef = ref<ColDef>({
      flex: 1,
      minWidth: 100,
      filter: true,
    });
    const rowData = ref<IOlympicData[]>(null);

    function onCellFocused(params) {
      const { rowIndex, rowPinned, column } = params;
      if (rowIndex == null || !column || typeof column === "string") {
        return;
      }
      lastFocusedCell = {
        rowIndex,
        rowPinned: rowPinned ?? null,
        column,
      };
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => (rowData.value = data);

      fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      columnDefs,
      tabToNextGridContainer,
      defaultColDef,
      rowData,
      onGridReady,
      onCellFocused,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Custom Tab to Next Grid Container](https://www.ag-grid.com/examples/keyboard-navigation/tab-to-next-grid-container/vue3)

### Custom Tabbing into the Grid

The `focusGridInnerElement` callback can be used to change the element focused by the grid when receiving focus from outside . Notice the following:

- Tabbing out of the input above the grid will focus the last focused cell if the grid was previously focused and the element is still in the DOM or otherwise the header.
- Shift Tabbing out of the input below the grid will focus the last focused cell if the grid was previously focused and the element is still in the DOM or otherwise the last cell in the bottom row.

#### Custom tabbing into the Grid

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  CellFocusedParams,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  Column,
  ColumnGroup,
  FocusGridInnerElement,
  FocusGridInnerElementParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  HeaderFocusedParams,
  ModuleRegistry,
  NumberEditorModule,
  NumberFilterModule,
  TextEditorModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  NumberEditorModule,
  TextEditorModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
]);

let lastFocused:
  | {
      column: string | Column | ColumnGroup | null;
      rowIndex?: number | null;
    }
  | undefined;

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="test-container">
      <div>
        <div class="form-container">
          <label>
            Input Above
            <input type="text">
            </label>
          </div>
        </div>
        <ag-grid-vue
          style="width: 100%; height: 100%;"
          @grid-ready="onGridReady"
          :columnDefs="columnDefs"
          :focusGridInnerElement="focusGridInnerElement"
          :defaultColDef="defaultColDef"
          :rowData="rowData"
          @cell-focused="onCellFocused"
          @header-focused="onHeaderFocused"></ag-grid-vue>
          <div class="form-container">
            <label>
              Input Below
              <input type="text">
              </label>
            </div>
          </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<IOlympicData> | null>(null);
    const columnDefs = ref<ColDef[]>([
      { headerName: "#", colId: "rowNum", valueGetter: "node.id" },
      { field: "athlete", minWidth: 170 },
      { field: "age" },
      { field: "country" },
      { field: "year" },
      { field: "date" },
      { field: "sport" },
      { field: "gold" },
      { field: "silver" },
      { field: "bronze" },
      { field: "total" },
    ]);
    const focusGridInnerElement = ref<FocusGridInnerElement>(
      (params: FocusGridInnerElementParams) => {
        if (!lastFocused || !lastFocused.column) {
          return false;
        }
        if (lastFocused.rowIndex != null) {
          params.api.setFocusedCell(
            lastFocused.rowIndex,
            lastFocused.column as Column | string,
          );
        } else {
          params.api.setFocusedHeader(lastFocused.column);
        }
        return true;
      },
    );
    const defaultColDef = ref<ColDef>({
      editable: true,
      flex: 1,
      minWidth: 100,
      filter: true,
    });
    const rowData = ref<IOlympicData[]>(null);

    function onCellFocused(params: CellFocusedParams) {
      lastFocused = { column: params.column, rowIndex: params.rowIndex };
    }
    function onHeaderFocused(params: HeaderFocusedParams) {
      lastFocused = { column: params.column, rowIndex: null };
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => (rowData.value = data);

      fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      columnDefs,
      focusGridInnerElement,
      defaultColDef,
      rowData,
      onGridReady,
      onCellFocused,
      onHeaderFocused,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Custom tabbing into the Grid](https://www.ag-grid.com/examples/keyboard-navigation/custom-tabbing-into-grid/vue3)

## Keyboard Events

It is possible to add custom behaviour to any key event that you want using the grid events `cellKeyDown` (gets called when a DOM `keyDown` event fires on a cell).

> **Note**
>
> These keyboard events are monitored by the grid panel, so they will not be fired when the `keydown` happens inside of a popup editor, as popup elements are rendered in a different DOM tree.

The grid events wrap the DOM events and provides additional information such as row and column details.

The example below shows processing grid cell keyboard events. The following can be noted:

- Each time a `cellKeyDown` is fired, the details of the event are logged to the console.
- When the user hits `S` on a row, the row selection is toggled.

#### Keyboard Events

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  CellKeyDownEvent,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  NumberFilterModule,
  RowSelectionModule,
  RowSelectionOptions,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  FiltersToolPanelModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  NumberFilterModule,
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  FiltersToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  SetFilterModule,
  RowSelectionModule,
]);

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :defaultColDef="defaultColDef"
      :rowSelection="rowSelection"
      :rowData="rowData"
      @cell-key-down="onCellKeyDown"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<IOlympicData> | null>(null);
    const columnDefs = ref<ColDef[]>([
      { field: "athlete", minWidth: 170 },
      { field: "age" },
      { field: "country" },
      { field: "year" },
      { field: "date" },
      { field: "sport" },
      { field: "gold" },
      { field: "silver" },
      { field: "bronze" },
      { field: "total" },
    ]);
    const defaultColDef = ref<ColDef>({
      flex: 1,
      minWidth: 100,
      filter: true,
    });
    const rowSelection = ref<RowSelectionOptions | "single" | "multiple">({
      mode: "singleRow",
      checkboxes: false,
    });
    const rowData = ref<IOlympicData[]>(null);

    function onCellKeyDown(e: CellKeyDownEvent) {
      console.log("onCellKeyDown", e);
      if (!e.event) {
        return;
      }
      const keyboardEvent = e.event as unknown as KeyboardEvent;
      const key = keyboardEvent.key;
      if (key.length) {
        console.log("Key Pressed = " + key);
        if (key === "s") {
          const rowNode = e.node;
          const newSelection = !rowNode.isSelected();
          console.log(
            "setting selection on node " +
              rowNode.data.athlete +
              " to " +
              newSelection,
          );
          rowNode.setSelected(newSelection);
        }
      }
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => (rowData.value = data);

      fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      rowSelection,
      rowData,
      onGridReady,
      onCellKeyDown,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Keyboard Events](https://www.ag-grid.com/examples/keyboard-navigation/keyboard-events/vue3)

## Suppress Keyboard Events

It is possible to stop the grid acting on particular events. To do this implement `colDef.suppressHeaderKeyboardEvent` and/or `colDef.suppressKeyboardEvent` callback. The callback should return `true` if the grid should suppress the events, or `false` to continue as normal.

### suppressHeaderKeyboardEvent

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `suppressHeaderKeyboardEvent` | `SuppressHeaderKeyboardEventFunc` |  |  | Suppress the grid taking action for the relevant keyboard event when a header is focused. |

### suppressKeyboardEvent

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `suppressKeyboardEvent` | `SuppressKeyboardEventFunc` |  | `false` | Allows the user to suppress certain keyboard events in the grid cell. |

The callback is available as a [column callback](https://www.ag-grid.com/vue-data-grid/column-properties/#reference-columns-suppressKeyboardEvent) (set on the column definition). If you want it to apply to all columns then apply to the `defaultColDef` property.

### Example: Suppress Keyboard Navigation

The example below demonstrates suppressing the following keyboard events:

- On the Athlete column cells only:
  - `↵ Enter` will not start or stop editing.
- On the Country column cells only:
  - `↑` `↓` arrow keys are allowed. This is the only column that allows navigation from the grid to the header.
- On all cells (including the cells of the Athlete Column):
  - `^ Ctrl`+`A` will not select all cells into a range.
  - `^ Ctrl`+`C` will not copy to clipboard.
  - `^ Ctrl`+`V` will not paste from clipboard.
  - `^ Ctrl`+`D` will not copy range down.
  - `Page Up` and `Page Down` will not get handled by the grid.
  - `Home` will not focus top left cell.
  - `End` will not focus bottom right cell.
  - `←` `↑` `→` `↓` Arrow keys will not navigate focused cell.
  - `F2` will not start editing.
  - `Delete` will not start editing.
  - `⌫ Backspace` will not start editing.
  - `⎋ Escape` will not cancel editing.
  - `␣ Space` will not select current row.
  - `⇥ Tab` will not be handled by the grid.
- On the Country header only:
  - Navigation is blocked from the left to right using arrows but is allowed using `⇥ Tab`.
  - Navigation up and down is allowed. This is the only header that allows navigation from the header to the grid cells.
  - `↵ Enter` is blocked. This is the only header that blocks sorting / opening menu via keyboard.
- On all headers (excluding country):
  - Navigation is blocked up and down, but navigation left / right is allowed using arrows and `⇥ Tab`.

#### Suppress Keys

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  NumberEditorModule,
  NumberFilterModule,
  RowSelectionModule,
  RowSelectionOptions,
  SuppressHeaderKeyboardEventParams,
  SuppressKeyboardEventParams,
  TextEditorModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  FiltersToolPanelModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  NumberEditorModule,
  TextEditorModule,
  NumberFilterModule,
  RowSelectionModule,
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  FiltersToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  SetFilterModule,
]);

function suppressEnter(params: SuppressKeyboardEventParams) {
  const KEY_ENTER = "Enter";
  const event = params.event;
  const key = event.key;
  const suppress = key === KEY_ENTER;
  return suppress;
}

function suppressNavigation(params: SuppressKeyboardEventParams) {
  const KEY_A = "A";
  const KEY_C = "C";
  const KEY_V = "V";
  const KEY_D = "D";
  const KEY_PAGE_UP = "PageUp";
  const KEY_PAGE_DOWN = "PageDown";
  const KEY_TAB = "Tab";
  const KEY_LEFT = "ArrowLeft";
  const KEY_UP = "ArrowUp";
  const KEY_RIGHT = "ArrowRight";
  const KEY_DOWN = "ArrowDown";
  const KEY_F2 = "F2";
  const KEY_BACKSPACE = "Backspace";
  const KEY_ESCAPE = "Escape";
  const KEY_SPACE = " ";
  const KEY_DELETE = "Delete";
  const KEY_PAGE_HOME = "Home";
  const KEY_PAGE_END = "End";
  const event = params.event;
  const key = event.key;
  let keysToSuppress = [
    KEY_PAGE_UP,
    KEY_PAGE_DOWN,
    KEY_TAB,
    KEY_F2,
    KEY_ESCAPE,
  ];
  const editingKeys = [
    KEY_LEFT,
    KEY_RIGHT,
    KEY_UP,
    KEY_DOWN,
    KEY_BACKSPACE,
    KEY_DELETE,
    KEY_SPACE,
    KEY_PAGE_HOME,
    KEY_PAGE_END,
  ];
  if (event.ctrlKey || event.metaKey) {
    keysToSuppress.push(KEY_A);
    keysToSuppress.push(KEY_V);
    keysToSuppress.push(KEY_C);
    keysToSuppress.push(KEY_D);
  }
  if (!params.editing) {
    keysToSuppress = keysToSuppress.concat(editingKeys);
  }
  if (
    params.column.getId() === "country" &&
    (key === KEY_UP || key === KEY_DOWN)
  ) {
    return false;
  }
  const suppress = keysToSuppress.some(function (suppressedKey) {
    return suppressedKey === key || key.toUpperCase() === suppressedKey;
  });
  return suppress;
}

function suppressUpDownNavigation(
  params: SuppressHeaderKeyboardEventParams,
): boolean {
  const key = params.event.key;
  return key === "ArrowUp" || key === "ArrowDown";
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :rowData="rowData"
      :defaultColDef="defaultColDef"
      :rowSelection="rowSelection"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<IOlympicData> | null>(null);
    const columnDefs = ref<ColDef[]>([
      {
        field: "athlete",
        minWidth: 170,
        suppressKeyboardEvent: (params) => {
          return suppressEnter(params) || suppressNavigation(params);
        },
      },
      { field: "age" },
      {
        field: "country",
        minWidth: 130,
        suppressHeaderKeyboardEvent: (params) => {
          const key = params.event.key;
          return key === "ArrowLeft" || key === "ArrowRight" || key === "Enter";
        },
      },
      { field: "year" },
      { field: "date" },
      { field: "sport" },
      { field: "gold" },
      { field: "silver" },
      { field: "bronze" },
      { field: "total" },
    ]);
    const defaultColDef = ref<ColDef>({
      editable: true,
      flex: 1,
      minWidth: 100,
      filter: true,
      suppressKeyboardEvent: suppressNavigation,
      suppressHeaderKeyboardEvent: suppressUpDownNavigation,
    });
    const rowSelection = ref<RowSelectionOptions | "single" | "multiple">({
      mode: "multiRow",
      checkboxes: false,
      headerCheckbox: false,
    });
    const rowData = ref<IOlympicData[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => (rowData.value = data);

      fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      columnDefs,
      rowData,
      defaultColDef,
      rowSelection,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Suppress Keys](https://www.ag-grid.com/examples/keyboard-navigation/suppress-keys/vue3)

## Custom Cell Component

When using custom Cell Components, the custom Cell Component is responsible for implementing support for keyboard navigation among its focusable elements. This is why by default, focusing a grid cell with a custom Cell Component will focus the entire cell instead of any of the elements inside the custom cell renderer.

Adding support for keyboard navigation and focus requires a custom `suppressKeyboardEvent` function in grid options. See [Suppress Keyboard Events](https://www.ag-grid.com/vue-data-grid/keyboard-navigation/#suppress-keyboard-events).

An example of this is shown below, enabling keyboard navigation through the custom cell elements when pressing `⇥ Tab` and `⇧ Shift`+`⇥ Tab`:

- Click on the top left `Natalie Coughlin` cell, press the `⇥ Tab` key and notice that the button, textbox and link can be tabbed into. At the end of the cell elements, the tab focus moves to the next cell in the next row
- Use `⇧ Shift`+`⇥ Tab` to navigate in the reverse direction

The `suppressKeyboardEvent` callback is used to capture tab events and determine if the user is tabbing forward or backwards. It also suppresses the default behaviour of moving to the next cell if tabbing within the child elements.

If the focus is at the beginning or the end of the cell children and moving out of the cell, the keyboard event is not suppressed, so focus can move between the children elements. Also, when moving backwards, the focus needs to be manually set while preventing the default behaviour of the keyboard press event.

#### Cell Renderer Keyboard Navigation

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  SuppressKeyboardEventParams,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import CustomElements from "./customElementsVue";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([TextFilterModule, ClientSideRowModelModule]);

const GRID_CELL_CLASSNAME = "ag-cell";

function getAllFocusableElementsOf(el: HTMLElement) {
  return Array.from<HTMLElement>(
    el.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
    ),
  ).filter((focusableEl) => {
    return focusableEl.tabIndex !== -1;
  });
}

function getEventPath(event: Event): HTMLElement[] {
  const path: HTMLElement[] = [];
  let currentTarget: any = event.target;
  while (currentTarget) {
    path.push(currentTarget);
    currentTarget = currentTarget.parentElement;
  }
  return path;
}

/**
 * Capture whether the user is tabbing forwards or backwards and suppress keyboard event if tabbing
 * outside of the children
 */
function suppressKeyboardEvent({ event }: SuppressKeyboardEventParams<any>) {
  const { key, shiftKey } = event;
  const path = getEventPath(event);
  const isTabForward = key === "Tab" && shiftKey === false;
  const isTabBackward = key === "Tab" && shiftKey === true;
  let suppressEvent = false;
  // Handle cell children tabbing
  if (isTabForward || isTabBackward) {
    const eGridCell = path.find((el) => {
      if (el.classList === undefined) return false;
      return el.classList.contains(GRID_CELL_CLASSNAME);
    });
    if (!eGridCell) {
      return suppressEvent;
    }
    const focusableChildrenElements = getAllFocusableElementsOf(eGridCell);
    const lastCellChildEl =
      focusableChildrenElements[focusableChildrenElements.length - 1];
    const firstCellChildEl = focusableChildrenElements[0];
    // Suppress keyboard event if tabbing forward within the cell and the current focused element is not the last child
    if (focusableChildrenElements.length === 0) {
      return false;
    }
    const currentIndex = focusableChildrenElements.indexOf(
      document.activeElement as HTMLElement,
    );
    if (isTabForward) {
      const isLastChildFocused =
        lastCellChildEl && document.activeElement === lastCellChildEl;
      if (!isLastChildFocused) {
        suppressEvent = true;
        if (currentIndex !== -1 || document.activeElement === eGridCell) {
          event.preventDefault();
          focusableChildrenElements[currentIndex + 1].focus();
        }
      }
    }
    // Suppress keyboard event if tabbing backwards within the cell, and the current focused element is not the first child
    else {
      const cellHasFocusedChildren =
        eGridCell.contains(document.activeElement) &&
        eGridCell !== document.activeElement;
      // Manually set focus to the last child element if cell doesn't have focused children
      if (!cellHasFocusedChildren) {
        lastCellChildEl.focus();
        // Cancel keyboard press, so that it doesn't focus on the last child and then pass through the keyboard press to
        // move to the 2nd last child element
        event.preventDefault();
      }
      const isFirstChildFocused =
        firstCellChildEl && document.activeElement === firstCellChildEl;
      if (!isFirstChildFocused) {
        suppressEvent = true;
        if (currentIndex !== -1 || document.activeElement === eGridCell) {
          event.preventDefault();
          focusableChildrenElements[currentIndex - 1].focus();
        }
      }
    }
  }
  return suppressEvent;
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :defaultColDef="defaultColDef"
      :rowData="rowData"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
    CustomElements,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      {
        field: "athlete",
      },
      {
        field: "country",
        flex: 1,
        cellRenderer: "CustomElements",
      },
    ]);
    const defaultColDef = ref<ColDef>({
      minWidth: 130,
      suppressKeyboardEvent,
    });
    const rowData = ref<any[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => {
        rowData.value = data;
      };

      fetch("https://www.ag-grid.com/example-assets/small-olympic-winners.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      rowData,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Cell Renderer Keyboard Navigation](https://www.ag-grid.com/examples/keyboard-navigation/cell-renderer-keyboard-navigation/vue3)
