{"action":"created","issue":{"url":"https://api.github.com/repos/whatwg/html/issues/2071","repository_url":"https://api.github.com/repos/whatwg/html","labels_url":"https://api.github.com/repos/whatwg/html/issues/2071/labels{/name}","comments_url":"https://api.github.com/repos/whatwg/html/issues/2071/comments","events_url":"https://api.github.com/repos/whatwg/html/issues/2071/events","html_url":"https://github.com/whatwg/html/issues/2071","id":190547071,"number":2071,"title":"Add support for determining which elements are focusable and tabable","user":{"login":"straker","id":2433219,"avatar_url":"https://avatars.githubusercontent.com/u/2433219?v=3","gravatar_id":"","url":"https://api.github.com/users/straker","html_url":"https://github.com/straker","followers_url":"https://api.github.com/users/straker/followers","following_url":"https://api.github.com/users/straker/following{/other_user}","gists_url":"https://api.github.com/users/straker/gists{/gist_id}","starred_url":"https://api.github.com/users/straker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/straker/subscriptions","organizations_url":"https://api.github.com/users/straker/orgs","repos_url":"https://api.github.com/users/straker/repos","events_url":"https://api.github.com/users/straker/events{/privacy}","received_events_url":"https://api.github.com/users/straker/received_events","type":"User","site_admin":false},"labels":[{"id":377109407,"url":"https://api.github.com/repos/whatwg/html/labels/accessibility","name":"accessibility","color":"fbca04","default":false},{"id":255178143,"url":"https://api.github.com/repos/whatwg/html/labels/addition/proposal","name":"addition/proposal","color":"d4c5f9","default":false},{"id":257427609,"url":"https://api.github.com/repos/whatwg/html/labels/needs%20implementor%20interest","name":"needs implementor interest","color":"5319e7","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":7,"created_at":"2016-11-20T08:53:04Z","updated_at":"2016-11-25T06:02:52Z","closed_at":null,"body":"## Proposal\r\n\r\nAdd support for determining which elements are focusable and tabable. Probably the most convenient API for developers is through a query selector since we'll need to generate a list of elements. A tree walker node filter would also be acceptable.\r\n\r\n## Why?\r\n\r\nFor accessibility reasons this is desperately needed. When implementing an [accessible modal](https://www.w3.org/TR/wai-aria-practices-1.1/#dialog_modal) it is recommended to auto focus the first focusable element when the modal is opened, as well as trap focus inside the modal.\r\n\r\n> When a modal dialog opens focus goes to the first focusable item in the dialog. Determining the first focusable item must take into account elements which receive focus by default (form fields and links) as well as items which may have a tabindex attribute with a positive value. If there is no focusable item in the dialog, focus is placed on the dialog container element.\r\n\r\n> Tab - Focus must be held within the dialog until it is cancelled or submitted. As the user presses tab to move within items in the dialog, pressing tab with focus on the last focusable item in the dialog will move focus back to the first focusable item in the dialog.\r\n\r\nHowever, there is no way to determine what elements are focusable. The current \"best\" way to do this is using [this answer from Stack Overflow](http://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus/1600194#1600194), which tries to build up a query selector of known focusable elements ([even Polymer uses this approach](https://github.com/PolymerElements/iron-overlay-behavior/blob/master/iron-focusables-helper.html#L46)). However, there is a huge flaw to this approach.\r\n\r\n(There's also [allyjs](https://allyjs.io/), but downloading a 20kb minified & gzipped library just to manage focus is a bit overkill. It also uses [a known list of focusable elements](https://github.com/medialize/ally.js/blob/master/src/selector/focusable.js), so in the end it's not any different then the Stack Overflow answer).\r\n\r\nThe flaw is that with custom elements, a known list of focusable elements is no longer possible. Take for example this simple custom element.\r\n\r\n```js\r\n<template id=\"search-element-template\">\r\n  <input type=\"search\">\r\n</template>\r\n\r\n<script>\r\n(function() {\r\n  var doc = (document._currentScript || document.currentScript).ownerDocument;\r\n  var template = doc.querySelector('#search-element-template');\r\n\r\n  customElements.define('search-element', class extends HTMLElement {\r\n    constructor() {\r\n      super();\r\n\r\n     this.attachShadow({\r\n        mode: 'open'\r\n      });\r\n    }\r\n\r\n    connectedCallback() {\r\n      const temp = document.importNode(template.content, true);\r\n      this.shadowRoot.appendChild(temp);\r\n    }\r\n  });\r\n})();\r\n</script>\r\n```\r\n\r\nWith the input field, the custom element is now focusable (`document.activeElement` will return the `search-element` when focus is on the input). This means any custom element could be focusable, making it impossible to use a whitelist of known native elements to determine focusability and tabablility. \r\n\r\nIf we can't reliably use a know list of selectors, that means that the only other way to know what is focusable is to actually test every element in the DOM to see if it moves the `document.activeElement`.\r\n\r\n```js\r\nlet focusable = [];\r\nlet els = document.body.querySelectorAll('*');\r\nfor (let i = 0; i < els.length; i++) {\r\n  els[i].focus();\r\n  if (els[i] === document.activeElement) {\r\n    focusable.push(els[i]);\r\n  }\r\n}\r\n```\r\n\r\nWhich of course is a terrible idea and will be slower the more elements your site has.\r\n\r\nRegardless, custom elements again make this difficult since calling `.focus()` on them doesn't do anything. You could try to see if the element had a `shadowRoot` and then traverse it's DOM for focusable elements, but using `attachShadow({mode: closed})` makes that impossible. The only way for custom elements to show that they are focusable is to use the little known [delegatesFocus](http://w3c.github.io/webcomponents/spec/shadow/#focus) property.\r\n\r\nIn the end, developers have no good way to make an accessible modal without the consumer of the modal marking all focusable elements (or at least, the first and last focusable elements), and ensuring all focusable custom elements use the delegatesFocus proeprty."},"comment":{"url":"https://api.github.com/repos/whatwg/html/issues/comments/262886602","html_url":"https://github.com/whatwg/html/issues/2071#issuecomment-262886602","issue_url":"https://api.github.com/repos/whatwg/html/issues/2071","id":262886602,"user":{"login":"straker","id":2433219,"avatar_url":"https://avatars.githubusercontent.com/u/2433219?v=3","gravatar_id":"","url":"https://api.github.com/users/straker","html_url":"https://github.com/straker","followers_url":"https://api.github.com/users/straker/followers","following_url":"https://api.github.com/users/straker/following{/other_user}","gists_url":"https://api.github.com/users/straker/gists{/gist_id}","starred_url":"https://api.github.com/users/straker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/straker/subscriptions","organizations_url":"https://api.github.com/users/straker/orgs","repos_url":"https://api.github.com/users/straker/repos","events_url":"https://api.github.com/users/straker/events{/privacy}","received_events_url":"https://api.github.com/users/straker/received_events","type":"User","site_admin":false},"created_at":"2016-11-25T06:02:52Z","updated_at":"2016-11-25T06:02:52Z","body":"Actually, if `getNextFocusableArea/getPreviousFocusableArea` were exposed to the client, then just those alone would fulfill the requirements for accessibility. For focusing the first element in the modal, all we would need to do is set focus to the modal using `<dialog>.focus()` and then call the `getNextFocusableArea` method, which would tell us the first focusable element in the modal. We could also call the same function in a loop to determine when the focus leaves the modal, or even call it every time `tab` is pressed to know when focus leaves the modal to know when to put focus back to the first element.\r\n\r\nFor focus areas not being nodes/elements (such as the video element with controls), would it be easy to always return the node/element? `document.activeElement` returns the video element even when the focus area is inside of the controls, would it be possible to do the same for the `getNextFocusableArea/getPreviousFocusableArea` api? It would just mean that the same node would be returned multiple times as focus moves from area to area."}}
{"actor":{"display_login":"straker"}}