Helsinki Design System
Helsinki Design System
Getting started
Tutorials
Guidelines
Visual assets
Design tokens
Components
OverviewAccordionButtonCardCheckboxDate inputDialogPrinciplesAccessibilityUsage & variationsDemos & APIDropdownFieldsetFooterIconKorosLoading spinnerLogoNavigationNotificationNumber inputPassword inputPhone inputRadio buttonSearch inputSelection groupStatus labelTabsTagText fieldsTime inputToggle buttonTooltip
Patterns
About
Contributing
Resources

Dialog

DraftAccessible

Dialogs initiate a conversation between the service and the user. They are used when an input or a confirmation is needed from the user or when important information needs to be conveyed.

Principles

  • Dialogs capture the browser focus and the user is forced to react on the dialog.
    • To emphasize this, a dark screen overlay must be used to cover the view content behind the dialog element.
  • Dialogs are very intrusive pattern and they should only be used when the immediate actions or focus from the user are needed.
  • Be careful when including a separate close ("x") icon in the dialog. If there are more than one action available in the dialog, it can be ambiguous for the user which action is triggered when the close icon is pressed.
    • Generally, it is a good practise to omit the close icon if the dialog has more than one action available.
  • If your dialog contains form elements, follow HDS form and form validation patterns similarly as you would in a regular forms.
  • As dialogs always contain buttons, pay close attention that the button labels describe the action it is going to trigger. You can read more information about this in the HDS Button documentation page.
    • Following the same guidelines as the HDS Form pattern, dialog action buttons are placed at the left side of the dialog and the primary action will be the first one from the left.
    • If some of the actions are destructive or irreversible, the button order should be reversed so so that the destructive actions are last in the button list. See Danger dialog example for more information.
  • Opening a dialog should be always triggered by the user. Do not open dialogs without user action.
    • Also make sure that the dialog is related to the current context.
    • It is not recommended to open dialogs on top of other dialogs. However, this is supported by HDS Dialogs if it is needed.

When to use each dialog type?

TypeWhen to use it?Example
InfoImportant information needs to be conveyed to the user. Only requires acknowledgement and no choices from the user.Informing the user about changed terms of use.
ConfirmAn action is required from the user.Confirming that the user wants to continue even though all form fields are not filled.
DangerAn action is required from the user while the action results are destructive.Confirming that the user really wants to delete a blog post.

Accessibility

  • When the dialog is closed, the focus should return to the element that triggered the original dialog context. This allows keyboard and screen reader users to continue the context where they originally opened the dialog.
    • If the dialog was opened on a page load, the focus should be returned to the start of the body element.
  • When the dialog is active, everything outside of the dialog should be inaccessible to the user.

Usage & variations

Info dialog

Info dialogs are used to convey important information to the user. Info dialogs only include one button which the user can use to acknowledge the information.

React code example:

{() => {
const openInfoDialogButtonRef = React.useRef(null);
const [isOpen, setIsOpen] = React.useState(false);
const close = () => setIsOpen(false);
const titleId = "info-dialog-title";
const descriptionId = "info-dialog-content";
return (
<>
<Button ref={openInfoDialogButtonRef} onClick={() => setIsOpen(true)}>
Open Info dialog
</Button>
<Dialog
id="info-dialog"
aria-labelledby={titleId}
aria-describedby={descriptionId}
isOpen={isOpen}
close={close}
closeButtonLabelText="Close info dialog"
focusAfterCloseRef={openInfoDialogButtonRef}
>
<Dialog.Header
id={titleId}
title="Terms of service have changed"
iconLeft={<IconInfoCircle aria-hidden="true" />}
/>
<Dialog.Content>
<p id={descriptionId} className="text-body">
Please note that the terms of this service have changed. You can review the changes in the user settings.
</p>
</Dialog.Content>
<Dialog.ActionButtons>
<Button onClick={close}>
Close
</Button>
</Dialog.ActionButtons>
</Dialog>
</>
)
}}

Confirm dialog

Confirm dialogs are used when an action is required from the user. Confirm dialogs always include at least two actions; one primary action (e.g. Confirm) and one secondary action (e.g. Cancel). However, more than two actions are allowed if it is needed.

React code example:

{() => {
const openConfirmationDialogButtonRef = React.useRef(null);
const [isOpen, setIsOpen] = React.useState(false);
const close = () => setIsOpen(false);
const titleId = "confirmation-dialog-title";
const descriptionId = "confirmation-dialog-info";
return (
<>
<Button ref={openConfirmationDialogButtonRef} onClick={() => setIsOpen(true)}>
Open Confirmation dialog
</Button>
<Dialog
id="confirmation-dialog"
aria-labelledby={titleId}
aria-describedby={descriptionId}
isOpen={isOpen}
focusAfterCloseRef={openConfirmationDialogButtonRef}
>
<Dialog.Header
id={titleId}
title="Are you sure you want to continue?"
iconLeft={<IconQuestionCircle aria-hidden="true" />}
/>
<Dialog.Content>
<p id={descriptionId} className="text-body">
You have not filled all form fields. Do you still want to continue? You can later return to edit this form. Saved forms can be accessed in your personal profile.
</p>
</Dialog.Content>
<Dialog.ActionButtons>
<Button onClick={close}>
Continue
</Button>
<Button onClick={close} variant="secondary">
Cancel
</Button>
</Dialog.ActionButtons>
</Dialog>
</>
)
}}

Danger dialog

Danger dialog is a variant of a confirm dialog. They are used in similar use cases but Danger dialogs are meant for situations where the action user is going to choose may be destructive or otherwise irreversible or very critical. Danger dialog emphasizes this by using HDS error status colours. Also, it reverses the action button order so that the destructive action is last in the button list.

React code example:

{() => {
const openDeleteDialogButtonRef = React.useRef(null);
const [isOpen, setIsOpen] = React.useState(false);
const close = () => setIsOpen(false);
const titleId = "delete-dialog-title";
const descriptionId = "delete-dialog-info";
return (
<>
<Button ref={openDeleteDialogButtonRef} onClick={() => setIsOpen(true)}>
Open Delete dialog
</Button>
<Dialog
variant="danger"
id="delete-dialog"
aria-labelledby={titleId}
aria-describedby={descriptionId}
isOpen={isOpen}
focusAfterCloseRef={openDeleteDialogButtonRef}
>
<Dialog.Header
id={titleId}
title="Are you sure you want to delete this blog post?"
iconLeft={<IconAlertCircle aria-hidden="true" />}
/>
<Dialog.Content>
<p id={descriptionId} className="text-body">
The blog post will be deleted immediately. Deletion is permanent and it cannot be reverted.
</p>
</Dialog.Content>
<Dialog.ActionButtons>
<Button onClick={close} theme="black" variant="secondary">
Cancel
</Button>
<Button
variant="danger"
iconLeft={<IconTrash aria-hidden="true" />}
onClick={() => {
// Add confirm operations here
close();
}}
>
Delete the blog post
</Button>
</Dialog.ActionButtons>
</Dialog>
</>
)
}}

Scrollable dialog

While not recommended, HDS supports scrollable dialogs if there is a large amount of content (e.g. terms of use). It is recommended to consider other options than a dialog to present the same data since it can be difficult for the user to form a clear understanding of the presented content.

React code example:

{() => {
const openTermsDialogButtonRef = React.useRef(null);
const [isOpen, setIsOpen] = React.useState(false);
const close = () => setIsOpen(false);
const titleId = "terms-dialog-title";
const descriptionId = "terms-dialog-info";
return (
<>
<Button ref={openTermsDialogButtonRef} onClick={() => setIsOpen(true)}>
Open Terms dialog
</Button>
<Dialog
id="terms-dialog"
aria-labelledby={titleId}
aria-describedby={descriptionId}
isOpen={isOpen}
focusAfterCloseRef={openTermsDialogButtonRef}
scrollable
>
<Dialog.Header
id={titleId}
title="Do you accept the terms of service?"
iconLeft={<IconAlertCircle aria-hidden="true" />}
/>
<Dialog.Content>
<h3 id={descriptionId}>Terms of service</h3>
<p className="text-body">
...
</p>
</Dialog.Content>
<Dialog.ActionButtons>
<Button onClick={close}>
Accept terms
</Button>
<Button onClick={close} variant="secondary">
Cancel
</Button>
</Dialog.ActionButtons>
</Dialog>
</>
)
}}

Dialog with custom content

HDS Dialog elements can be used to build custom dialogs. Using HDS provided elements ensures the base level accessibility for the components. They also visually match to other HDS components.

React code example:

{() => {
const openCustomDialogButtonRef = React.useRef(null);
const [isOpen, setIsOpen] = React.useState(false);
const close = () => setIsOpen(false);
const titleId = "custom-dialog-title";
const descriptionId = "custom-dialog-info";
return (
<>
<Button ref={openCustomDialogButtonRef} onClick={() => setIsOpen(true)}>
Open Dialog with custom content
</Button>
<Dialog
id="custom-dialog"
aria-labelledby={titleId}
aria-describedby={descriptionId}
isOpen={isOpen}
focusAfterCloseRef={openCustomDialogButtonRef}
>
<Dialog.Header
id={titleId}
title="Add new item"
/>
<Dialog.Content>
<p id={descriptionId} className="text-body">
Add a new item by filling the information below. All fields are mandatory.
</p>
<TextInput
id="item-name"
label="Item name"
placeholder="E.g. Item 1"
helperText="Item's name must be unique."
required
/>
<br />
<TextArea
id="item-description"
label="Item description"
placeholder="E.g. Item 1 is the first item of the system."
required
/>
</Dialog.Content>
<Dialog.ActionButtons>
<Button
onClick={() => {
// Add operations here
close();
}}
>
Add item
</Button>
<Button onClick={close} variant="secondary">
Cancel
</Button>
</Dialog.ActionButtons>
</Dialog>
</>
)
}}

Demos & API

Core

Not included in hds-core!

React

Dialog in hds-react

Dialog API