AlertDialog
componentA question that interrupts, asked in a modal window on the native dialog element: Escape dismisses it, a click outside does not, and the focus starts on the answer that does no harm.
Preview
Code
<twig:AlertDialog id="delete">
<twig:AlertDialog:Trigger>
<twig:Button variant="destructive" {{ ...alert_dialog_trigger_attrs }}>Delete</twig:Button>
</twig:AlertDialog:Trigger>
<twig:AlertDialog:Content>
<twig:AlertDialog:Header>
<twig:AlertDialog:Title>Delete the project?</twig:AlertDialog:Title>
<twig:AlertDialog:Description>Its pages and files go with it.</twig:AlertDialog:Description>
</twig:AlertDialog:Header>
<twig:AlertDialog:Footer>
<twig:AlertDialog:Cancel>Keep it</twig:AlertDialog:Cancel>
<twig:AlertDialog:Action variant="destructive" type="submit" form="delete-project">Delete</twig:AlertDialog:Action>
</twig:AlertDialog:Footer>
</twig:AlertDialog:Content>
</twig:AlertDialog>
Code
<twig:AlertDialog id="delete">
<twig:AlertDialog:Trigger>
<twig:Button variant="destructive" {{ ...alert_dialog_trigger_attrs }}>Delete</twig:Button>
</twig:AlertDialog:Trigger>
<twig:AlertDialog:Content>
<twig:AlertDialog:Header>
<twig:AlertDialog:Title>Delete the project?</twig:AlertDialog:Title>
<twig:AlertDialog:Description>Its pages and files go with it.</twig:AlertDialog:Description>
</twig:AlertDialog:Header>
<twig:AlertDialog:Footer>
<twig:AlertDialog:Cancel>Keep it</twig:AlertDialog:Cancel>
<twig:AlertDialog:Action variant="destructive" type="submit" form="delete-project">Delete</twig:AlertDialog:Action>
</twig:AlertDialog:Footer>
</twig:AlertDialog:Content>
</twig:AlertDialog>
Try it
Change the props; the example is rendered again by the server.
<twig:AlertDialog id="delete">
<twig:AlertDialog:Trigger>
<twig:Button variant="destructive" {{ ...alert_dialog_trigger_attrs }}>Delete</twig:Button>
</twig:AlertDialog:Trigger>
<twig:AlertDialog:Content>
<twig:AlertDialog:Header>
<twig:AlertDialog:Title>Delete the project?</twig:AlertDialog:Title>
<twig:AlertDialog:Description>Its pages and files go with it.</twig:AlertDialog:Description>
</twig:AlertDialog:Header>
<twig:AlertDialog:Footer>
<twig:AlertDialog:Cancel>Keep it</twig:AlertDialog:Cancel>
<twig:AlertDialog:Action variant="destructive" type="submit" form="delete-project">Delete</twig:AlertDialog:Action>
</twig:AlertDialog:Footer>
</twig:AlertDialog:Content>
</twig:AlertDialog>
<twig:AlertDialog id="delete">
<twig:AlertDialog:Trigger>
<twig:Button variant="destructive" {{ ...alert_dialog_trigger_attrs }}>Delete</twig:Button>
</twig:AlertDialog:Trigger>
<twig:AlertDialog:Content>
<twig:AlertDialog:Header>
<twig:AlertDialog:Title>Delete the project?</twig:AlertDialog:Title>
<twig:AlertDialog:Description>Its pages and files go with it.</twig:AlertDialog:Description>
</twig:AlertDialog:Header>
<twig:AlertDialog:Footer>
<twig:AlertDialog:Cancel>Keep it</twig:AlertDialog:Cancel>
<twig:AlertDialog:Action variant="destructive" type="submit" form="delete-project">Delete</twig:AlertDialog:Action>
</twig:AlertDialog:Footer>
</twig:AlertDialog:Content>
</twig:AlertDialog>
Variants
open
Whether the alert dialog is open on initial render.
Installation
php bin/console ux:install alert-dialog --kit=github.com/parler-haut-interagir-librement/ux-kit
It brings the recipes it needs: button, dialog
Props
| Prop | Type | Default | Description |
|---|---|---|---|
open |
bool |
false |
Whether the alert dialog is open on initial render. |
Accessibility
The window has the role alertdialog, is named by its title and described by its description. It carries closedby="closerequest": Escape closes it, a click on the backdrop does not, so that the question is answered. The cancel button has the focus when it opens. The buttons carry command and commandfor, which the browser runs itself where it can; the Dialog controller, which the recipe installs, stands in for the rest.
Files to copy
templates/components/AlertDialog.html.twig
{%- props
## string Unique identifier used to generate internal AlertDialog IDs.
id,
## bool Whether the alert dialog is open on initial render.
open = false
-%}
{%- set _dialog_id = 'alert-dialog-' ~ id -%}
{%- do provide('alertDialog.id', _dialog_id) -%}
{%- do provide('alertDialog.titleId', _dialog_id ~ '-title') -%}
{%- do provide('alertDialog.descriptionId', _dialog_id ~ '-description') -%}
<div data-slot="alert-dialog" data-dialog-open-value="{{ open ? 'true' : 'false' }}"{{ attributes.defaults({class: 'hasDialog', 'data-controller': 'dialog'}) }}>
{##- The alert dialog structure, typically includes `AlertDialog:Trigger` and `AlertDialog:Content`. -#}
{%- block content %}{% endblock -%}
</div>
templates/components/AlertDialog/Action.html.twig
{%- props
## 'default'|'secondary'|'destructive'|'outline'|'ghost'|'link' The visual style variant of the button.
variant = 'default'
-%}
<twig:Button :variant="variant" data-slot="alert-dialog-action" data-action="click->dialog#close" command="close" commandfor="{{ inject('alertDialog.id') }}" {{ ...attributes }}>
{##- The label of the action the alert dialog asks to confirm. -#}
{{- block(outerBlocks.content) -}}
</twig:Button>
templates/components/AlertDialog/Cancel.html.twig
{#-
# The button that answers no: it has the focus when the alert dialog opens, the least harmful answer being the one a
# stray Enter gives.
#}
{%- props
## 'default'|'secondary'|'destructive'|'outline'|'ghost'|'link' The visual style variant of the button.
variant = 'outline'
-%}
<twig:Button :variant="variant" autofocus data-slot="alert-dialog-cancel" data-action="click->dialog#close" command="close" commandfor="{{ inject('alertDialog.id') }}" {{ ...attributes }}>
{##- The label of the button that dismisses the alert dialog. -#}
{{- block(outerBlocks.content) -}}
</twig:Button>
templates/components/AlertDialog/Content.html.twig
{#-
# The window of an alert dialog: Escape closes it, as a request to close, but a click on the backdrop does not, so
# that the question is answered by one of its buttons.
#}
<dialog id="{{ inject('alertDialog.id') }}" role="alertdialog" closedby="closerequest" aria-labelledby="{{ inject('alertDialog.titleId') }}" aria-describedby="{{ inject('alertDialog.descriptionId') }}" data-slot="alert-dialog-content" data-dialog-target="dialog"{{ attributes.defaults({class: 'isDialog', 'data-action': 'close->dialog#closed'}) }}>
{##- The alert dialog content, typically an `AlertDialog:Header` and an `AlertDialog:Footer`. -#}
{%- block content %}{% endblock -%}
</dialog>
templates/components/AlertDialog/Description.html.twig
<p id="{{ inject('alertDialog.descriptionId') }}" data-slot="alert-dialog-description"{{ attributes.without('id').defaults({class: 'isDialogDescription'}) }}>
{##- What follows from the answer. -#}
{%- block content %}{% endblock -%}
</p>
templates/components/AlertDialog/Footer.html.twig
<footer data-slot="alert-dialog-footer"{{ attributes.defaults({class: 'isDialogFooter'}) }}>
{##- The footer area, typically contains `AlertDialog:Cancel` and `AlertDialog:Action`. -#}
{%- block content %}{% endblock -%}
</footer>
templates/components/AlertDialog/Header.html.twig
<header data-slot="alert-dialog-header"{{ attributes.defaults({class: 'isDialogHeader'}) }}>
{##- The header area, typically contains `AlertDialog:Title` and `AlertDialog:Description`. -#}
{%- block content %}{% endblock -%}
</header>
templates/components/AlertDialog/Title.html.twig
<h2 id="{{ inject('alertDialog.titleId') }}" data-slot="alert-dialog-title"{{ attributes.without('id').defaults({class: 'isDialogTitle'}) }}>
{##- The question the alert dialog asks. -#}
{%- block content %}{% endblock -%}
</h2>
templates/components/AlertDialog/Trigger.html.twig
{%- set alert_dialog_trigger_attrs = {
'data-slot': 'alert-dialog-trigger',
'data-action': 'click->dialog#open',
'data-dialog-target': 'trigger',
'aria-haspopup': 'dialog',
command: 'show-modal',
commandfor: inject('alertDialog.id'),
} -%}
{##- The trigger element (e.g., a `Button`) that opens the alert dialog when clicked. -#}
{%- block content %}{% endblock -%}