8

Note: Existing questions exists here and elsewhere but they are all jQuery specific and there is no canonical answer covering plain JavaScript, including support for web components.

I'd like to simulate a tab key press so that the focus is shifted to the next element in the tab order. The next element could be any HTML focusable HTML element or an element with tabindex="0". This also needs to work where some descendant HTML elements could be custom web components with shadow DOM's.

The potential solution could be to fire a key down event or iterating over descendant nodes looking for one that is focusable.

Muhammad Rehan Saeed
  • 35,627
  • 39
  • 202
  • 311
  • This looks like a _please do my job_ question. What have you tried? Use the [<>] button in the SO editor to add a running snippet. – Danny '365CSI' Engelman Sep 30 '22 at 11:38
  • 1
    I've tried the solutions in the post linked in particular and some derivatives. Also tried selecting the first descendant with a tabindex greater than -1. There is no canonical answer for this on StackOverflow which seems like a glaring omission for what seems a common technical question. The whole point of this site is to answer dev questions and help me "do my job". Nothing wrong with that. – Muhammad Rehan Saeed Sep 30 '22 at 13:33

2 Answers2

1

I've tried to make the keyboard events work but sadly I can't get it to work nicely. Based on the docs I believe it the correct way to do it would be:

document.activeElement.dispatchEvent(new KeyboardEvent("keypress", { 
    key: "Tab" 
});
// -----------------------------------------------------
// Find all the elements that have a tabindex set, 
// I would keep this file scoped for performance sake
// ------------------------------------------------------
const tabElements = Array.from(document
    // Get all elements that can be focusable
  .querySelectorAll('a, button, input, textarea, select, details, [tabindex]'))
  
  // remove any that have a tabIndex of -1
  .filter(element => element.tabIndex > -1)
  
  // split elements into two arrays, explicit tabIndexs and implicit ones
  .reduce((prev, next) => {  
      return next.tabIndex > 0
        ? [[...prev[0], next].sort((a, b) => a.tabIndex > b.tabIndex ? -1 : 1), prev[1]]
        : [prev[0], [...prev[1], next]];
      }, [[], []])
      
  // flatten the two-dimensional array
    .flatMap(element => element);

then selecting the next element:

// ------------------------------------------------------------------------------
// Method to find the next element to focus and change the focus to that element
// ------------------------------------------------------------------------------
const DispatchTab = () => {

    // If the current focused element is a -1 then we wont find the index in the elements list, got to the start
    if (document.activeElement.tabIndex === -1) {
    tabElements[0].focus();
    return;
  }

    // find the current index in the tab list of the currently focused element
    const currentIndex = tabElements.findIndex(e => e === document.activeElement);

  // get the next element in the list ("%" will loop the index around to 0)
  const nextIndex = (currentIndex + 1) % tabElements.length;
  tabElements[nextIndex].focus();
}

I have created a JS fiddle with an approach that doesn't use the KeyboardEvent at all. It might need tweaking to your liking but should do the job. I feel Tab Example

Simon Curtis
  • 382
  • 2
  • 9
0

You can dispatch a KeyboardEvent, with altKey: true

element.dispatchEvent(new KeyboardEvent('keydown', {altKey: true}))
Mina
  • 14,386
  • 3
  • 13
  • 26