Core Dialog
<dialog>
is an element with which the user interacts with to perform some task or decision. @nrk/core-dialog
simply adds dialog
functionality to a <dialog>
if it is not supported (or extends functionality if is supported). @nrk/core-dialog
supports nestability, keyboard navigation containment and restoring focus when dialog is closed.
Installation
npm install @nrk/core-dialog --save-exact
import coreDialog from '@nrk/core-dialog'
import CoreDialog from '@nrk/core-dialog/jsx'
Demo
<button data-core-dialog="my-dialog">Open dialog</button>
<dialog id="my-dialog" class="my-dialog" aria-label="første dialog tittel">
<h1>This is a title</h1>
<p>Nunc mi felis, condimentum quis hendrerit sed, porta eget libero. Aenean scelerisque ex eu nisi varius hendrerit. Suspendisse elementum quis massa at vehicula. Nulla lacinia mi pulvinar, venenatis nisi ut, commodo quam. Praesent egestas mi sit amet quam porttitor, mollis mattis mi rhoncus.</p>
<button data-core-dialog="my-dialog-nested">Open an additional dialog</button>
<button type="button" autofocus style="visibility: hidden">Should not be focusable</button>
<button type="button" autofocus>Autofocus</button>
<button data-core-dialog="close">Close</button>
</dialog>
<dialog id="my-dialog-nested" class="my-dialog" aria-label="andre dialog tittel">
<h1>Another dialog, triggered inside the first dialog</h1>
<p>Nunc mi felis, condimentum quis hendrerit sed, porta eget libero.</p>
<button data-core-dialog="close">Close</button>
</dialog>
<button data-core-dialog="strict-dialog">Open strict dialog</button>
<dialog id="strict-dialog" class="my-dialog" aria-label="første dialog tittel">
<h1>This is a title</h1>
<p>Nunc mi felis, condimentum quis hendrerit sed, porta eget libero. Aenean scelerisque ex eu nisi varius hendrerit. Suspendisse elementum quis massa at vehicula. Nulla lacinia mi pulvinar, venenatis nisi ut, commodo quam. Praesent egestas mi sit amet quam porttitor, mollis mattis mi rhoncus.</p>
<button type="button">This button does nothing</button>
<button data-core-dialog="close">Close</button>
</dialog>
<div id="docs-react-dialog"></div>
<script>
coreDialog('#my-dialog');
coreDialog('#my-dialog-nested');
coreDialog('#strict-dialog', { strict: true });
</script>
<div id="jsx-dialog"></div>
<div id="jsx-dialog-strict"></div>
<script type="text/jsx">
class DialogContainerDemo extends React.Component {
constructor (props) {
super(props)
this.state = {
open: false,
contentTitle: 'Dialog for JSX'
}
this.toggleDialog = this.toggleDialog.bind(this)
this.handleToggle = this.handleToggle.bind(this)
}
toggleDialog () {
this.setState({open: !this.state.open})
}
handleToggle (event) {
event.preventDefault()
this.setState({open: !event.detail.isOpen})
}
render () {
return (
<div>
<button onClick={this.toggleDialog}>Open dialog jsx</button>
<CoreDialog
className="my-dialog"
open={this.state.open}
onToggle={this.handleToggle}
aria-label="React dialog"
>
<h1>{this.state.contentTitle}</h1>
<p>Nunc mi felis, condimentum quis hendrerit sed, porta eget libero. Aenean scelerisque ex eu nisi varius hendrerit. Suspendisse elementum quis massa at vehicula. Nulla lacinia mi pulvinar, venenatis nisi ut, commodo quam. Praesent egestas mi sit amet quam porttitor, mollis mattis mi rhoncus.</p>
<button onClick={this.toggleDialog}>Lukk</button>
</CoreDialog>
</div>
)
}
}
class StrictDialogContainerDemo extends React.Component {
constructor (props) {
super(props);
this.state = {
open: false,
contentTitle: 'Strict dialog for JSX'
}
this.toggleDialog = this.toggleDialog.bind(this)
}
toggleDialog () {
this.setState({open: !this.state.open})
}
render () {
return (
<div>
<button data-core-dialog="dialog-jsx">Open strict dialog jsx</button>
<CoreDialog
id="dialog-jsx"
className="my-dialog"
onToggle={this.handleStrictToggle}
aria-label="React dialog"
strict>
<h1>{this.state.contentTitle}</h1>
<p>Nunc mi felis, condimentum quis hendrerit sed, porta eget libero. Aenean scelerisque ex eu nisi varius hendrerit. Suspendisse elementum quis massa at vehicula. Nulla lacinia mi pulvinar, venenatis nisi ut, commodo quam. Praesent egestas mi sit amet quam porttitor, mollis mattis mi rhoncus.</p>
<button data-core-dialog="close">Lukk</button>
</CoreDialog>
</div>
)
}
}
ReactDOM.render(<DialogContainerDemo />, document.getElementById('jsx-dialog'))
ReactDOM.render(<StrictDialogContainerDemo />, document.getElementById('jsx-dialog-strict'))
</script>
Usage
HTML / JavaScript
<button data-core-dialog="my-dialog">Open dialog</button>
<dialog id="my-dialog">
<h1>Title of dialog</h1>
<p>Some content</p>
<button data-core-dialog="close">Close dialog</button>
</dialog>
import coreDialog from '@nrk/core-dialog'
coreDialog(String|Element|Elements, {
open: Boolean,
strict: Boolean,
label: String,
modal: Boolean,
opener: Element
})
coreDialog('.my-dialog')
coreDialog('.my-dialog', true)
coreDialog('.my-dialog', { open: true, label: 'A super dialog' })
React / Preact
import CoreDialog from '@nrk/core-dialog/jsx'
<CoreDialog open={true|false} strict={true|false} modal={true|false} onToggle={function(){}} aria-label="Title of dialog">
<h1>My React/Preact dialog</h1>
<p>Some content</p>
<button onClick={closeDialog}></button>
</CoreDialog>
function closeDialog (event) {
}
Markup
Required focusable element
Your dialog must contain <input>
, <button>
, <select>
, <textarea>
, <a>
or element with tabindex="-1"
to ensure the user is navigated into the @nrk/nrk-dialog
.
As a best practice; if your dialog contains a form element, use autofocus
.
If you dialog is without form elements, start your dialog
content with <h1 tabindex="-1">Dialog title</h1>
.
Supporting IE9
If you need @nrk/core-dialog
to support IE9, add the following code in your <head>
tag:
<!--[if IE 9]><script>document.createElement('dialog')</script><![endif]-->
Elements order
Though not strictly required, the <button>
opening a @nrk/core-dialog
should be placed directly before the <dialog>
itself. This eases the mental model for screen reader users.
Events
document.addEventListener('dialog.toggle', (event) => {
event.target
event.detail.isOpen
event.detail.willOpen
})
Styling
.my-dialog {}
.my-dialog[open] {}
.my-dialog + backdrop {}
.my-dialog + backdrop[hidden] {}
Note : There is a z-index limit for the backdrop at 2000000000. Do not use higher z-index values in your site in order for core-dialog
to work properly. The limit exists because some browser extensions, like ghostery have absurdly high z-indexes. The issue is further explained here.
FAQ
Why use <dialog>
when it is not supported by all browsers?
There is currently minimal support for the <dialog>
element. However, to ensure best accessibility, @nrk/core-dialog
uses native functionality whenever possible, and augments with role="dialog"
as a fallback. For more information about the dialog element visit the W3C HTML 5.2 specification. For more information about WAI-ARIA practices for the dialog element visit the W3C WAI-ARIA Authoring Practices 1.1