Skip to content

Commit

Permalink
Drop support for the native dialog HTML element
Browse files Browse the repository at this point in the history
  • Loading branch information
KittyGiraudel committed Feb 27, 2021
1 parent b753a0a commit d38994a
Show file tree
Hide file tree
Showing 12 changed files with 18 additions and 272 deletions.
69 changes: 3 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# [A11y Dialog](http://hugogiraudel.github.io/a11y-dialog/)

[a11y-dialog](http://hugogiraudel.github.io/a11y-dialog/) is a lightweight (1.4Kb) yet flexible script to create accessible dialog windows.
[a11y-dialog](http://hugogiraudel.github.io/a11y-dialog/) is a lightweight (1.3Kb) yet flexible script to create accessible dialog windows.

✔︎ Fully compliant with the [WAI-ARIA Authoring Practices 1.2](https://www.w3.org/TR/wai-aria-practices-1.2/#dialog_modal)
✔︎ Closing dialog on overlay click and <kbd>ESC</kbd>
✔︎ Trapping and restoring focus
✔︎ Firing events
✔︎ DOM and JS APIs
✔︎ Fast and tiny
✔︎ Leveraging the native `<dialog>` element if desired
✔︎ Fast and tiny

You can try the [live demo ↗](http://hugogiraudel.github.io/a11y-dialog/example/).

Expand Down Expand Up @@ -117,12 +116,10 @@ Here is the basic markup, which can be enhanced. Pay extra attention to the comm
- It has to have the `aria-modal="true"` attribute so the rest of the page cannot be interacted with.
- It may have the `alertdialog` role to make it behave like a “modal”. See the [Usage as a modal](#usage-as-a-modal) section of the docs.
- It doesn’t have to have the `aria-labelledby` attribute however this is recommended. It should match the `id` of the dialog title.
- It can be a `<dialog>` element, but [it is not recommended](#using-the-dialog-html-element).

5. The inner document.

- It doesn’t have to exist but improves support in NVDA.
- It doesn’t have to exist when using `<dialog>` because is implied.

6. The dialog close button.

Expand Down Expand Up @@ -367,72 +364,12 @@ By default, a11y-dialog behaves as a dialog: it is closable with the <kbd>ESC</k

To do so:

1. Replace `role="dialog"` with `role="alertdialog"`. This will make sure <kbd>ESC</kbd> doesn’t close the modal. Note that this role does not work properly with the [native `<dialog>` element](#using-the-dialog-html-element) so make sure to use `<div role="alertdialog">`.
1. Replace `role="dialog"` with `role="alertdialog"`. This will make sure <kbd>ESC</kbd> doesn’t close the modal.
2. Remove `data-a11y-dialog-hide` from the overlay element. This makes sure it is not possible to close the modal by clicking outside of it.
3. In case the user actively needs to operate with the modal, you might consider removing the close button from it. Be sure to still offer a way to eventually close the modal.

For more information about modals, refer to the [WAI ARIA recommendations](https://www.w3.org/TR/wai-aria-1.1/#alertdialog).

### Using the dialog HTML element

As mentioned in the [HTML section](#html-boilerplate), the script works fine with the native HTML `<dialog>` element and will polyfill its behaviour so the dialog works in any browser, regardless of their support for that HTML element. However, it is recommended _not_ to use it and to rely on a `<div>` with `role="dialog"` instead. Amongst other, here are the issues with the HTML `<dialog>` element:

- Clicking the backdrop does not close the dialog on Chrome.
- The native `::backdrop` only shows when programatically opening the dialog, not when using the `open` attribute.
- Default styles are left to the browsers’ discretion and can be inconsistent.
- The [modal pattern](#usage-as-a-modal) (`role="alertdialog"`) simply does not work with the dialog element.
- It still requires JavaScript anyway, so it’s not even 100% HTML.
- [Read more about the shortcoming of the dialog element by Scott O'hara](https://www.scottohara.me/blog/2019/03/05/open-dialog.html).

If you really want to use the `<dialog>` HTML element nevertheless, here are a few things you should know.

The [provided base styles](#styling) will not quite work because the dialog container does not receive the `aria-hidden` attribute when hidden. That is because the dialog’s visibility is handled by the user-agent itself. This means the container is essentially always displayed. For that reason, it should not made fixed on top of everything, otherwise it prevents interacting with the page at all.

Fortunately, the library adds a `data-a11y-dialog-native` attribute (with no value) when the `<dialog>` element is used and natively supported. This attribute can be used to customise the styling layer based on user-agent support (or lack thereof).

The following styles are more suited to using `<dialog>`.

```css
/**
* 1. When the native `<dialog>` element is supported and used, the overlay is
* handled natively and can be styled with `::backdrop`, which means the DOM
* one should be removed. Feel free to replace `:first-child` with the
* overlay selector of your choice.
*/
[data-a11y-dialog-native] > :first-child {
display: none; /* 1 */
}

/**
* 1. Absolutely center the dialog on top of the page.
*/
dialog {
position: fixed; /* 1 */
top: 50%; /* 1 */
left: 50%; /* 1 */
transform: translate(-50%, -50%); /* 1 */
z-index: 2; /* 1 */
}

/**
* 1. When the `<dialog>` element is used but not supported by the user agent,
* its default display is `inline` which can cause layout issues. This makes
* sure the dialog is correctly displayed when open.
*/
dialog[open] {
display: block; /* 1 */
}

/**
* 1. Make the overlay look like an overlay.
*/
dialog::backdrop {
background-color: rgba(43, 46, 56, 0.9); /* 1 */
}
```

When the `<dialog>` element is used and natively supported, the argument passed to `show()` and `hide()` is being passed to the native call to [`showModal()`](https://www.w3.org/TR/html52/interactive-elements.html#dom-htmldialogelement-showmodal) and [`close()`](https://www.w3.org/TR/html52/interactive-elements.html#dom-htmldialogelement-close). If necessary, the `returnValue` can be read using `<instance>.dialog.returnValue`.

### Nested dialogs

Nesting dialogs is a [questionable design pattern](https://ux.stackexchange.com/questions/52042/is-it-acceptable-to-open-a-modal-popup-on-top-of-another-modal-popup) that is not referenced anywhere in the [HTML 5.2 Dialog specification](https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element). Therefore it is actively discouraged in favour of clearer interface design, but it technically is supported by the library.
Expand Down
1 change: 0 additions & 1 deletion SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
- [Events](README.md#events)
- [Animations](README.md#animations)
- [Usage as a modal](README.md#usage-as-a-modal)
- [Using the dialog HTML element](README.md#using-the-dialog-html-element)
- [Nested dialogs](README.md#nested-dialogs)

- [Further reading](README.md#further-reading)
Expand Down
34 changes: 3 additions & 31 deletions a11y-dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ function A11yDialog(node) {

// Keep a reference of the node and the actual dialog on the instance
this.container = node
this.dialog = node.querySelector(
'dialog, [role="dialog"], [role="alertdialog"]'
)
this.dialog = node.querySelector('[role="dialog"], [role="alertdialog"]')
this.role = this.dialog.getAttribute('role') || 'dialog'
this.useDialog = 'show' in this.dialog

// Keep an object of listener types mapped to callback functions
this._listeners = {}
Expand All @@ -39,19 +36,6 @@ function A11yDialog(node) {
* @return {this}
*/
A11yDialog.prototype.create = function () {
if (this.useDialog) {
this.container.setAttribute('data-a11y-dialog-native', '')

// Despite using a `<dialog>` element, `role="dialog"` is not necessarily
// implied by all screen-readers (yet)
// See: https://github.com/HugoGiraudel/a11y-dialog/commit/6ba711a777aed0dbda0719a18a02f742098c64d9#commitcomment-28694166
this.dialog.setAttribute('role', this.role)

// Remove initial `aria-hidden` from container
// See: https://github.com/HugoGiraudel/a11y-dialog/pull/117#issuecomment-706056246
this.container.removeAttribute('aria-hidden')
}

// Keep a collection of dialog openers, each of which will be bound a click
// event listener to open the dialog
this._openers = $$('[data-a11y-dialog-show="' + this.container.id + '"]')
Expand Down Expand Up @@ -96,13 +80,7 @@ A11yDialog.prototype.show = function (event) {
// Keep a reference to the currently focused element to be able to restore
// it later
this._previouslyFocused = document.activeElement

if (this.useDialog) {
this.dialog.showModal(event instanceof Event ? void 0 : event)
} else {
this.dialog.setAttribute('open', '')
this.container.removeAttribute('aria-hidden')
}
this.container.removeAttribute('aria-hidden')

// Set the focus to the first focusable child of the dialog element
setFocusToFirstItem(this.dialog)
Expand Down Expand Up @@ -134,13 +112,7 @@ A11yDialog.prototype.hide = function (event) {
}

this.shown = false

if (this.useDialog) {
this.dialog.close(event instanceof Event ? void 0 : event)
} else {
this.dialog.removeAttribute('open')
this.container.setAttribute('aria-hidden', 'true')
}
this.container.setAttribute('aria-hidden', 'true')

// If there was a focused element before the dialog was opened (and it has a
// `focus` method), restore the focus back to it
Expand Down
27 changes: 0 additions & 27 deletions cypress/integration/dialogElement.js

This file was deleted.

34 changes: 3 additions & 31 deletions dist/a11y-dialog.esm.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d38994a

Please sign in to comment.