# Perspective Table Row Navigation using Keyboard

**URL:** <https://forum.inductiveautomation.com/t/perspective-table-row-navigation-using-keyboard/102849>\
**Category:** Ignition\
**Tags:** perspective, ignition81\
**Created:** [February 25, 2025, 4:06pm UTC](https://forum.inductiveautomation.com/t/perspective-table-row-navigation-using-keyboard/102849 "2025-02-25T16:06:30Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![tecnoeletronic](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/tecnoeletronic/32/50975_2.png) [@tecnoeletronic](https://forum.inductiveautomation.com/u/tecnoeletronic)\
**Post date:** [February 25, 2025, 4:06pm UTC](https://forum.inductiveautomation.com/t/perspective-table-row-navigation-using-keyboard/102849/1 "2025-02-25T16:06:30Z")

</div>

In Perspective, the Table control does not seem to allow row navigation using the keyboard (arrow up-down) but only by selecting the row with the mouse. Is this the expected behavior?

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [February 26, 2025, 4:34am UTC](https://forum.inductiveautomation.com/t/perspective-table-row-navigation-using-keyboard/102849/2 "2025-02-26T04:34:01Z")

</div>

> [@tecnoeletronic](#):
>
> Is this the expected behavior?

Yep 🙂 I haven't used a SCADA platform with this ability, not saying there isn't one, but I wouldn't expect it. I certainly haven't seen many web brower tables with this functionality

Edit: although there do seem to be some cool js/react tables out there with this functionality

---

<div class="post-metadata">

**Author:** ![tecnoeletronic](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/tecnoeletronic/32/50975_2.png) [@tecnoeletronic](https://forum.inductiveautomation.com/u/tecnoeletronic)\
**Post date:** [February 26, 2025, 5:38am UTC](https://forum.inductiveautomation.com/t/perspective-table-row-navigation-using-keyboard/102849/3 "2025-02-26T05:38:07Z")

</div>

Thank you for the confirmation.

---

<div class="post-metadata">

**Author:** ![olivier.cote](https://avatars.discourse-cdn.com/v4/letter/o/e9bcb4/32.png) [@olivier.cote](https://forum.inductiveautomation.com/u/olivier.cote)\
**Post date:** [July 9, 2025, 2:35pm UTC](https://forum.inductiveautomation.com/t/perspective-table-row-navigation-using-keyboard/102849/4 "2025-07-09T14:35:34Z")

</div>

I had the same question.

I had resolve this by adding a sessions Events, Key Event.  
I send a 'page' message. The table catch it and change the selectedRow.

---

<div class="post-metadata">

**Author:** ![Ryan\_Deardorff](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan_deardorff/32/63815_2.png) [@Ryan\_Deardorff](https://forum.inductiveautomation.com/u/Ryan_Deardorff)\
**Post date:** [July 9, 2025, 2:39pm UTC](https://forum.inductiveautomation.com/t/perspective-table-row-navigation-using-keyboard/102849/5 "2025-07-09T14:39:28Z")

</div>

Not sure why you would use a Session Event. I've done this using an Event on the table itself...

---

<div class="post-metadata">

**Author:** ![AdriaLuque](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/adrialuque/32/55726_2.png) [@AdriaLuque](https://forum.inductiveautomation.com/u/AdriaLuque)\
**Post date:** [March 27, 2026, 11:43am UTC](https://forum.inductiveautomation.com/t/perspective-table-row-navigation-using-keyboard/102849/6 "2026-03-27T11:43:23Z")

</div>

A client asked for the same thing, and we solved it with an event on the table doing +/-1 to the `props.selection.selectedRow` depending on the pressed arrow (`event.key`) like you described.

However, if a user filters or sorts the table without modifying the underlying query, `selectedRow` still refers to the original `row` index of the `props.data` dataset in the original order, not the visual `rowIndex` of the data in the displayed order. This means that pressing up/down can make the selected row jump around or disappear (when it refers to a filtered row), which is obviously not the desired behavior. Is there a way to solve this issue?

My best leads right now are:

1. The fact that some events can return `row` and `rowIndex` separately (like clicking on a row, but not events like pressing a key or changing selection):  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/b/9/b9725c61f01004997672ed3c3129318eb4fb6207.png)
2. When using subviews, the two are default input parameters too, so I can create a column that shows the original `row` and the current `rowIndex` together like this: ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/f/4/f44d6e3d499fef114686ebce2ab8a62c930c1c11.png)

As an example of the desired behavior, if `selectedRow=88` and the user presses the down arrow key, it needs to change into 92, not 89. My idea is to find the `rowIndex` of `row=88`, which is `1`, then add 1, then find the `row` of `rowIndex=2`, which is `92`.

But I haven’t found a direct way to find these properties and do an easy mapping. Making each subview send a message back to the main table with its own pair to reconstruct an array seems like it could easily overload the system. Is there something I’m missing?

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [April 2, 2026, 9:30am UTC](https://forum.inductiveautomation.com/t/perspective-table-row-navigation-using-keyboard/102849/7 "2026-04-02T09:30:10Z")

</div>

When you say you want to use the keys to navigate rows, do you just mean like this?

There are some bugs if the table is virtualised – i.e. it will not work properly. Same for tables with a pager probably…

If so, copy this markdown component into your View.

> **Markdown Component JSON**
>
> ```json
> [
> {
> "type": "ia.display.markdown",
> "version": 0,
> "props": {
> "style": {
> "position": "absolute"
> },
> "markdown": {
> "escapeHtml": false
> }
> },
> "meta": {
> "name": "JSInject_Table_AddArrowKeyRowSelection"
> },
> "position": {
> "shrink": 0,
> "basis": 0
> },
> "custom": {
> "inlineJavascript": "(() => {\n const view = [...window.__client.page.views._data.values()].find(view => view.value.mountPath == this.parentNode.parentNode.parentNode.getAttributeNode('data-component-path').value.split('.')[0]).value;\n const grid = document.querySelector('.ReactVirtualized__Grid');\n const table = document.querySelector('.ia_table');\n\n if (!grid || !table) return;\n\n grid.setAttribute('tabindex', '0');\n\n let selectedRowIndex = -1;\n\n function getRows() {\n return table.querySelectorAll('.ia_table __body__ rowGroup');\n }\n\n function selectRow(index) {\n const rows = getRows();\n if (rows.length === 0) return;\n\n index = Math.max(0, Math.min(index, rows.length - 1));\n\n rows.forEach(row => {\n row.style.outline = '';\n row.style.backgroundColor = '';\n });\n\n const target = rows[index];\n target.style.outline = '2px solid #0078d4';\n target.style.backgroundColor = 'rgba(0, 120, 212, 0.15)';\n\n selectedRowIndex = index;\n target.scrollIntoView({ block: 'nearest', behavior: 'smooth' });\n view.custom.write('selectedRow', selectedRowIndex);\n }\n\n grid.addEventListener('keydown', (e) => {\n const rows = getRows();\n if (rows.length === 0) return;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n selectRow(selectedRowIndex < 0 ? 0 : selectedRowIndex + 1);\n break;\n case 'ArrowUp':\n e.preventDefault();\n selectRow(selectedRowIndex <= 0 ? 0 : selectedRowIndex - 1);\n break;\n case 'Home':\n e.preventDefault();\n selectRow(0);\n break;\n case 'End':\n e.preventDefault();\n selectRow(rows.length - 1);\n break;\n }\n });\n\ntable.addEventListener('click', (e) => {\n const clickedRow = e.target.closest('.ia_table __body__ rowGroup');\n if (!clickedRow) return;\n const rows = Array.from(getRows());\n const index = rows.indexOf(clickedRow);\n if (index !== -1) selectRow(index);\n});\n\n grid.focus();\n})()"
> },
> "propConfig": {
> "props.source": {
> "binding": {
> "config": {
> "struct": {
> "script": "{this.custom.inlineJavascript}"
> },
> "waitOnAll": true
> },
> "transforms": [
> {
> "code": "#\tcode = \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '&quot;') + '\\\"></img>'\n#\t\n#\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \")\n#\t\n#\treturn code\n\tscript = value.script\n\tscript = script.replace('\"', '&quot;')\n\tscript = script.replace('\\n', '').replace('\\t', '').replace('\\r', '')\n\t\n\t# Collapse multiple spaces - repeat until stable\n\twhile ' ' in script:\n\t script = script.replace(' ', ' ')\n\t\n\tcode = '<img style=\"display:none\" src=\"/favicon.ico\" onload=\"{}\"></img>'.format(script)\n\treturn code",
> "type": "script"
> }
> ],
> "type": "expr-struct"
> }
> }
> }
> }
> ]
> 
> ```

Note: this will write the selected row index with the keyboard to view.custom.selectedRow. You can bidirectionally bind your table’s props.selection.selectedRow to this.

---

<div class="post-metadata">

**Author:** ![AdriaLuque](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/adrialuque/32/55726_2.png) [@AdriaLuque](https://forum.inductiveautomation.com/u/AdriaLuque)\
**Post date:** [April 10, 2026, 8:34am UTC](https://forum.inductiveautomation.com/t/perspective-table-row-navigation-using-keyboard/102849/8 "2026-04-10T08:34:14Z")

</div>

Wow, thanks!

However, I tried it and ran into a couple of issues. I created a new view, inserted a default table component and copied this markdown over. Then I made the bidirectional binding to `selectedRow` as indicated. Following this comment:

> There are some bugs if the table is virtualised – i.e. it will not work properly. Same for tables with a pager probably…

I disabled the pager and set `props.virtualized = false` on the table and tried on preview mode, but nothing moved when pressing the arrow keys. Then I changed it back to true and it worked like in your video, which was promising.

However, when sorting or filtering the issue is the same as with a simple event changing `selectedRow`, doing index+/-1 is based on the original order, not the visual one. Maybe that’s to be expected when the table is virtualized, and I missed a step to make this work, but I’m unsure. Hopefully this video helps see what’s happening:

  
When I sort by “city”, Folsom (`row=0`) visually shows up at the 11th row (`rowIndex=10`), but when pressing down, setting `selectedRow=10+1` makes the table select `row=11`, **not** `rowIndex=11`, and thus the selection changes to what was the original 12th row, Delhi, which now is actually a couple rows before Folsom. Unless this is the bug you mentioned when the table is virtualized, we should obtain the `row` value of `rowIndex=11` first and then write that into `selectedRow` so it selects Guadalajara.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/b/7/b757f4684f96cf6b84df8300f5a865b4e057c691.jpeg)

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/f/6/f6946f697762a6a583865a3a19059cc6d2f7c2c2.jpeg)

Please let me know if it didn’t work like this on your end and if you see anything I did wrong. I’m testing with subviews, where we can use both properties, and messages to send the index back and forth, will report back if it works. Thanks again for looking into this!
