Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SvelteKit should not set tabIndex=-1 #3501

Closed
rgossiaux opened this issue Jan 22, 2022 · 5 comments · Fixed by #4140
Closed

SvelteKit should not set tabIndex=-1 #3501

rgossiaux opened this issue Jan 22, 2022 · 5 comments · Fixed by #4140

Comments

@rgossiaux
Copy link

Describe the bug

SvelteKit adds tabIndex=-1 to the document body in order to be able to manage focus upon navigation: #307

This is not a "safe" change, in that it may impact the behavior of components on the page. In particular, this change means that some actions that would normally not trigger a focus event (like mousedown on some text) will now fire a focus event on the document body. These extra focus events may confuse code that does its own focus management (for example, a focus trap). They were the source of two different user-reported bugs in my component library: rgossiaux/svelte-headlessui#36 and rgossiaux/svelte-headlessui#39

Looking at the motivation for introducing this originally, I don't see why it can't be replaced with something better. Can SvelteKit clean up after itself after navigation by removing the tabIndex? Alternatively, can it do something that doesn't require setting this at all? For example, Headless UI has a function to focus on the first focusable element within a container: https://github.com/tailwindlabs/headlessui/blob/cae976a18b7955670cd28fb1067a49917bda2c09/packages/%40headlessui-react/src/utils/focus-management.ts#L105 This works by using a CSS selector to identify potentially-focusable elements, then attempting to focus them one by one until a success.

Reproduction

Not sure this needs a special test repro since this happens on every SvelteKit app, but I can create one if you disagree.

The bugs that this caused can be reproduced at this commit: https://github.com/rgossiaux/svelte-headlessui/tree/ac1f86ac156a94c86e81647c55d8bb56c035edc6

Logs

No response

System Info

System:
    OS: macOS 12.1
    CPU: (10) arm64 Apple M1 Max
    Memory: 4.11 GB / 64.00 GB
    Shell: 3.3.1 - /opt/homebrew/bin/fish
  Binaries:
    Node: 16.8.0 - /var/folders/rn/2kdgb0jj25q2v656j3qc_ywr0000gn/T/fnm_multishells/23841_1640400023703/bin/node
    Yarn: 1.22.17 - /opt/homebrew/bin/yarn
    npm: 7.21.0 - /var/folders/rn/2kdgb0jj25q2v656j3qc_ywr0000gn/T/fnm_multishells/23841_1640400023703/bin/npm
  Browsers:
    Chrome: 97.0.4692.99
    Safari: 15.2

Severity

serious, but I can work around it

Additional Information

No response

@benmccann
Copy link
Member

The line in question:

document.body.setAttribute('tabindex', '-1');

It looks like this was added in #309 to implement #307

@geoffrich explained the motivation for this here: #3381 (comment)

This isn't the section of code I'm most familiar with, but I hope that helps describe the original goals / motivations.

Happy to consider PRs to improve this!

@geoffrich
Copy link
Member

I think we should take another look at this behavior. It seems to be causing some obscure issues (see also #1699, where the root cause is a Chrome bug, but one that only happens when <body> has tabindex=-1). Setting tabindex on the body also means that when you click anywhere on the page, your focus is reset to the top, instead of continuing from where you clicked.

A mitigation might be only setting tabindex on the body when it's about to be focused, and then removing it after the focus has occurred. This will require testing with various browsers and screen readers to make sure focus is set appropriately. However, I think a more long term solution will be rethinking our focus management solution to be more robust (and customizable by the user).

@am283721

This comment has been minimized.

@dimfeld
Copy link
Contributor

dimfeld commented Feb 26, 2022

Setting tabindex on the body also means that when you click anywhere on the page, your focus is reset to the top, instead of continuing from where you clicked.

I've found that this particular behavior causes problems with mixed mouse/keyboard navigation. If you click on a scrollable region and then try to use the arrow keys to scroll within the region, the body scrolls instead (or nothing happens if body isn't scrollable).

If anyone else is running into issues like this, it can be worked around by also adding tabindex="-1" on the scrollable regions' tags, like so: <section style="overflow-y:auto" tabindex="-1">...</section>

@mrkishi
Copy link
Member

mrkishi commented Feb 26, 2022

Since there are a few issues caused by tabindex="-1", at this point we should question whether there are any issues by removing the attribute after the reset, as suggested by @rgossiaux.

const doc = document.documentElement;
doc.setAttribute('tabindex', '-1');
doc.focus();
doc.removeAttribute('tabindex');

I did some tests and, informally, it seems to work just as well. It didn't seem to trip Narrator or NVDA, and worked across Chromium and Firefox (don't know about Safari). I'll PR this change soon, but we should probably look for adverse effects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants