Core Dialog
@nrk/core-dialog
is an elevated element with which the user interacts with to perform some task or decision.
It supports nestability, keyboard navigation containment and restoring focus when dialog is closed.
Example
<button for="my-dialog">Open dialog</button>
<core-dialog id="my-dialog" class="my-dialog" aria-label="første dialog tittel" hidden>
<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 for="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 for="close">Close</button>
<core-dialog id="my-dialog-nested" class="my-dialog" aria-label="andre dialog tittel" hidden>
<h1>Another dialog, triggered inside the first dialog</h1>
<p>Nunc mi felis, condimentum quis hendrerit sed, porta eget libero.</p>
<button for="close">Close</button>
</core-dialog>
</core-dialog>
<button for="strict-dialog">Open strict dialog</button>
<core-dialog id="strict-dialog" class="my-dialog" aria-label="første dialog tittel" hidden strict>
<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 for="close">Close</button>
</core-dialog>
<div id="docs-react-dialog"></div>
<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 }
this.toggleDialog = this.toggleDialog.bind(this)
this.handleToggle = this.handleToggle.bind(this)
}
toggleDialog () {
this.setState({ open: !this.state.open })
}
handleToggle (event) {
this.setState({ open: event.target.open })
}
render () {
return (
<div>
<button onClick={this.toggleDialog}>Open dialog jsx</button>
<CoreDialog
className="my-dialog"
hidden={!this.state.open}
onDialogToggle={this.handleToggle}
aria-label="React dialog">
<h1>Dialog for JSX</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>
)
}
}
ReactDOM.render(<DialogContainerDemo />, document.getElementById('jsx-dialog'))
ReactDOM.render(<div>
<button for="dialog-jsx">Open strict dialog jsx</button>
<CoreDialog hidden id="dialog-jsx" className="my-dialog" aria-label="React dialog" strict>
<h1>Strict dialog for JSX</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 for="close">Lukk</button>
</CoreDialog>
</div>, document.getElementById('jsx-dialog-strict'))
</script>
Installation
Using NPM provides own element namespace and extensibility.
Recommended:
npm install @nrk/core-dialog
Using static registers the custom element with default name automatically:
<script src="https://static.nrk.no/core-components/major/6/core-dialog/core-dialog.min.js"></script>
Usage
HTML / JavaScript
<button for="my-dialog">Open</button>
<core-dialog id="my-dialog"
hidden <!-- Hide dialog by default -->
strict
modal
aria-label="{String}">
<h1>Title of dialog</h1>
<p>Some content</p>
<button for="close">Close dialog</button>
</core-dialog>
import CoreDialog from '@nrk/core-dialog'
window.customElements.define('core-dialog', CoreDialog)
const myDialog = document.querySelector('core-dialog')
myDialog.open
myDialog.modal
myDialog.strict
myDialog.backdrop
myDialog.open = false
myDialog.modal = true
myDialog.strict = false
myDialog.hidden = true
myDialog.close()
myDialog.show()
myDialog.showModal()
React / Preact
import CoreDialog from '@nrk/core-dialog/jsx'
<button for="my-dialog">Open</button> // Opens dialog with id="my-dialog"
<CoreDialog id="my-dialog"
hidden
strict
modal
aria-label={String}
onDialogToggle={Function}>
<h1>My React/Preact dialog</h1>
<p>Some content</p>
<button for="close"></button>
</CoreDialog>
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 <core-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>
.
Elements order
Though not strictly required, the <button>
opening a dialog should be placed directly before the <core-dialog>
itself. This eases the mental model for screen reader users.
Events
dialog.toggle
Fired when a dialog is toggled:
document.addEventListener('dialog.toggle', (event) => {
event.target
})
Styling
.my-dialog {}
.my-dialog[hidden] {}
.my-dialog:not([hidden]) {}
.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.