Canvas "tabindex"=1

We have been trying to make our WebGL-based app pass accessibility standards. In the process we have noticed that at some point the html canvas element gets the attribute of “tabindex”=1 added to it. Specifically it appears to gets this attribute the first time the mouse pointer touches the canvas.

You can recreate this on the playground by:
1: Open up Chrome and go ahead and show your page inspector
2: Go to this simple example: Babylon.js Playground
NOTE! Be very careful to not let your mouse cross into the canvas area or you will have to refresh the page
3: In your page inspector find the div element where id=“canvasZone”. Expand it so you can see the canvas element below it
4: Note that the canvas element does not have an attribute of “tabindex”=1. If it does, refresh the page and check again
5: Now move your mouse over the canvas area. As soon as the mouse crosses on to the canvas you will note the attribute is added.

If we manually override the tabindex with another number (ie -1) it gets set back to 1.

Does anyone have insight as to why this is happening? Is a tabindex of 1 required for the engine or is there a trick to get it to be different?

From searching the babylon.max.js file, the tabIndex is set here:

No idea why though.

1 Like

We need it to be able to get keyboard events :slight_smile:

1 Like

Here’s some additional information for anyone else who may be looking into this same question:

A positive tabindex should not required to receive keyboard events or for the canvas to gain focus. A tabindex > 0 is not advised by Accessibility Experts as it often leads to an unexpected tab order.

From WebAIM:

The tabindex attribute has three distinct uses:

  • tabindex="1" (or any number greater than 1) defines an explicit tab order. This is almost always a bad idea.
  • tabindex="0" allows elements besides links and form elements to receive keyboard focus. It does not change the tab order, but places the element in the logical navigation flow, as if it were a link on the page.
  • tabindex="-1" allows things besides links and form elements to receive “programmatic” focus, meaning focus can be set to the element through scripting, links, etc.

The BabylonJS team added a new property to the engine that allows us to set the canvas’ tabindex value. This property can be found in version 4.1.0-beta.21 and newer.

2 Likes

Thanks all for the discussion so far, and kintz for linking the new property, but I’m not entirely clear on the resolution of this thread.

I’m doing an accessibility pass on a web app and was also surprised to see tabindex=“1” applied to our canvas on mouseover.

After upgrading our project to use BabylonJS v4.1.0, I find that setting engine.canvasTabIndex = 0 allows me to keep the canvas at its correct position in the tab navigation sequence for our site, seemingly without interfering with Babylon receiving keyboard events once it is focused.

This is in conflict with Deltakosh’s answer above, as well as the comment on canvasTabIndex in the source: “Gets or sets the tab index to set to the rendering canvas. 1 is the minimum value to set to be able to capture keyboard events

Am I missing something? Will this come back to bite me later? Or is using canvasTabIndex = 0 copacetic (as is suggested by kintz’s answer being marked as a solution)?

1 Like

Hey @ashtm, sorry I didn’t see your comment sooner. I agree that this is confusing when looking at the comment in the source.

1 is the minimum value to set to be able to capture keyboard events

I don’t believe this to be accurate, but I’m more on the UX/UI side of things than the 3D engine. @Deltakosh or anyone know where this recommendation originally comes from?

The structure of the project does play a factor here. In one of our apps we don’t have any GUI in the engine, all UI is an HTML overlay while the 3D scene is in the engine. On that webpage, the tabindex is -1 since there is no need for the element to be focused. With the canvas’ tabindex at -1, we still receive keyboard and mouse input to interact with the 3D scene.

Most keyboard events in babylon.js are registered on the document object or the window object. so tabIndex won’t influence them.
I guess it might be an issue with GUI text inputs, but they use different HTML elements AFAIK that are already focusable, so… I don’t think this comment is correct, but this is an uneducated guess, i have to admit.

Hi, so ive been looking through various babylonjs resources about accessiblity.

There are totally reasonable things about the difficulty of making 3d spaces interface well with accessibility tech and standards.

Many people agree that it is usually easier to create somekind of html interface to act for accessiblity support.

This issue here is in direct contravention of that. It is a huge body of work and experience that has gone into makeing web pages accessible. It is not possible to work with elements that have a tabindex of “1”…

Im sort of expecting a bit more care to be taken here, if BabylonJS are saying “use html for accesiblity” and this literally the only thing that you have to make work with the standards :slight_smile:

thank you for the amazing software

EDIT: It seems that the engine aggressively continues to set the tabindex=‘1’ whenever it is changed.

I dont know if you have studied accessiblity, but it is not possible to create a modal window that is keyboard accessible if there are elements that have tabindex > 0… this is the point here

EDIT: and that the address bar of chrome comes before tabindex 1? in the tab order of the page. so the tabbing order goes “stuff we have control over, SSL button, address bar, babylonjs canvas, back to the beginning

Hey @christopherreay, I think that the reason why the engine is continuing to set the tabIndex to 1 is that the value for the engine’s canvasTabIndex has a default value of 1. Since this value is used in _processPointerMove to set the canvas’s tabIndex, this would explain why any changes to the tabIndex, would be overwritten. Updating this value (engine.canvasTabIndex) to the desired one should make sure that the desired tabIndex is retained.

1 Like

The following is a hack to get rid of the tabindex when more control of app interactions are desired:

        const fake = document.createElement('div');
        const engine = babylonScene.getEngine();
        const canvas = engine.getRenderingCanvas();
        const original = engine.getInputElement;
        // Remove the intial value Babylon adds.
        canvas.removeAttribute('tabindex');
        // Before Babylon's event handlers run, we fake its getInputElement
        // method so that it applies tabindex and focus to an element that isn't
        // in the DOM. Then we restore the method after the event completes, as
        // it is needed for other purposes.
        canvas.addEventListener('pointermove', () => (engine.getInputElement = () => fake), { capture: true });
        canvas.addEventListener('pointermove', () => (engine.getInputElement = original), { capture: false });
        canvas.addEventListener('wheel', () => (engine.getInputElement = () => fake), { capture: true });
        canvas.addEventListener('wheel', () => (engine.getInputElement = original), { capture: false });

Is there a better way I may have overlooked?

There is a canvasTabIndex property on engine that can be used to set the tabindex attribute of the HTML canvas element.

That doesn’t get rid of the tabIndex. My previous code snippet prevents the canvas from even having a tabIndex at all (that was the intent).