View as Markdown
Modal
Use the modal component to show interactive dialogs and notifications to your website users available in multiple sizes, colors, and styles
100%
Loading...
<div style="min-height: 304px">
<twig:Modal id="delete_account">
<twig:Modal:Trigger>
<twig:Button variant="outline" {{ ...modal_trigger_attrs }}>Open Modal</twig:Button>
</twig:Modal:Trigger>
<twig:Modal:Content>
<twig:Modal:Header>
<twig:Modal:Title>Edit profile</twig:Modal:Title>
</twig:Modal:Header>
<twig:Modal:Body>
<p class="leading-relaxed text-body">
With less than a month to go before the European Union enacts new consumer privacy laws for its citizens, companies around the world are updating their terms of service agreements to comply.
</p>
<p class="leading-relaxed text-body">
The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is meant to ensure a common set of data rights in the European Union. It requires organizations to notify users as soon as possible of high-risk data breaches that could personally affect them.
</p>
</twig:Modal:Body>
<twig:Modal:Footer>
<twig:Button type="submit">I accept</twig:Button>
<twig:Modal:Close>
<twig:Button variant="outline" {{ ...modal_close_attrs }}>Decline</twig:Button>
</twig:Modal:Close>
</twig:Modal:Footer>
</twig:Modal:Content>
</twig:Modal>
</div>
Installation
Note
Available since UX Toolkit 2.35.
php bin/console ux:install modal --kit flowbite-4
Install the following Composer dependencies:
composer require symfony/ux-icons twig/extra-bundle twig/html-extra:^3.24.0 symfony/ux-twig-component:^3.5 tales-from-a-dev/twig-tailwind-extra:^1.3.0
Install the following front-end dependencies:
npm install flowbite
# ...or with Symfony AssetMapper
php bin/console importmap:require flowbite
Copy the following file(s) into your app:
assets/controllers/flowbite_modal_controller.js
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static targets = ['trigger', 'modal'];
static values = {
open: Boolean,
};
connect() {
if (this.openValue) {
this.open();
}
}
open() {
this.modalTarget.showModal();
if (this.hasTriggerTarget) {
if (this.modalTarget.getAnimations().length > 0) {
this.modalTarget.addEventListener(
'transitionend',
() => {
this.triggerTarget.setAttribute('aria-expanded', 'true');
this.modalTarget.setAttribute('aria-hidden', 'false');
},
{ once: true }
);
} else {
this.triggerTarget.setAttribute('aria-expanded', 'true');
this.modalTarget.setAttribute('aria-hidden', 'false');
}
}
}
closeOnClickOutside({ target }) {
if (target === this.modalTarget) {
this.close();
}
}
close() {
this.modalTarget.close();
if (this.hasTriggerTarget) {
if (this.modalTarget.getAnimations().length > 0) {
this.modalTarget.addEventListener(
'transitionend',
() => {
this.triggerTarget.setAttribute('aria-expanded', 'false');
this.modalTarget.setAttribute('aria-hidden', 'true');
},
{ once: true }
);
} else {
this.triggerTarget.setAttribute('aria-expanded', 'false');
this.modalTarget.setAttribute('aria-hidden', 'true');
}
}
}
}
templates/components/Modal.html.twig
{%- props
## boolean Whether the modal is open on initial render.
open = false,
## string Unique identifier used to generate internal Modal IDs.
id
-%}
{%- set _modal_id = 'modal-' ~ id -%}
{%- set _modal_title_id = _modal_id ~ '-title' -%}
<div
data-flowbite-modal-open-value="{{ open ? 'true' : 'false' }}"
aria-labelledby="{{ _modal_title_id }}"
{{ attributes.defaults({
'data-controller': 'flowbite-modal',
}) }}
>
{## The modal structure, typically includes `Modal:Trigger` and `Modal:Content`. -#}
{% block content %}{% endblock %}
</div>
templates/components/Modal/Body.html.twig
<div
{{ attributes.defaults({
class: 'space-y-4 md:space-y-6 py-4 md:py-6'|tailwind_classes,
}) }}
>
{##- The main content area of the moda. -#}
{%- block content %}{% endblock -%}
</div>
templates/components/Modal/Close.html.twig
{%- set modal_close_attrs = {
'data-action': 'click->flowbite-modal#close',
} -%}
{##- The close trigger element (e.g., a `Button`) that closes the modal when clicked. -#}
{%- block content %}{% endblock -%}
templates/components/Modal/Content.html.twig
{%- props
## boolean Whether to display the close button in the top-right corner.
showCloseButton = true,
## 'dynamic'|'static' To prevent the modal from closing when clicking outside.
backdrop = 'dynamic'
-%}
<dialog
id="{{ _modal_id }}"
tabindex="-1"
aria-hidden="true"
class="{{ ('relative bg-neutral-primary-soft text-body border border-default rounded-base shadow-sm p-4 md:p-6 m-auto z-50 backdrop:transition-discrete backdrop:duration-150 open:backdrop:bg-dark-backdrop/70 ' ~ attributes.render('class'))|tailwind_merge }}"
data-flowbite-modal-target="modal"
data-action="keydown.esc->flowbite-modal#close:prevent {{ backdrop is not same as('static') ? 'click->flowbite-modal#closeOnClickOutside' }}"
>
{##- The modal content, typically includes `Modal:Header` and optionally `Modal:Footer`. -#}
{%- block content %}{% endblock -%}
{% if showCloseButton %}
<twig:Button type="button" variant="ghost" size="icon-xs" class="absolute top-4 right-4" data-action="click->flowbite-modal#close">
<twig:ux:icon name="flowbite:close-outline" class="size-4" />
<span class="sr-only">Close</span>
</twig:Button>
{% endif %}
</dialog>
templates/components/Modal/Footer.html.twig
<footer
{{ attributes.defaults({
class: 'flex items-center border-t border-default space-x-4 pt-4 md:pt-5'|tailwind_classes,
}) }}
>
{##- The footer area, typically contains action buttons. -#}
{%- block content %}{% endblock -%}
</footer>
templates/components/Modal/Header.html.twig
<header
{{ attributes.defaults({
class: 'flex items-center justify-between border-b border-default pb-4 md:pb-5'|tailwind_classes,
}) }}
>
{##- The header area, typically contains `Modal:Title` and `Modal:Description`. -#}
{%- block content %}{% endblock -%}
</header>
templates/components/Modal/Title.html.twig
<h3
id="{{ _modal_title_id }}"
{{ attributes.without('id').defaults({class: 'text-lg font-medium text-heading'|tailwind_classes}) }}
>
{##- The title text of the modal. -#}
{%- block content %}{% endblock -%}
</h3>
templates/components/Modal/Trigger.html.twig
{%- set modal_trigger_attrs = {
'data-action': 'click->flowbite-modal#open'|html_attr_type('sst'),
'data-flowbite-modal-target': 'trigger',
'aria-haspopup': 'dialog',
} -%}
{##- The trigger element (e.g., a `Button`) that opens the modal when clicked. -#}
{%- block content %}{% endblock -%}
Usage
<twig:Modal id="delete_account">
<twig:Modal:Trigger>
<twig:Button {{ ...modal_trigger_attrs }}>Open</twig:Button>
</twig:Modal:Trigger>
<twig:Modal:Content>
<twig:Modal:Header>
<twig:Modal:Title>Are you absolutely sure?</twig:Modal:Title>
</twig:Modal:Header>
</twig:Modal:Content>
</twig:Modal>
Examples
Static modal
Use the prop backdrop="static" to prevent the modal from closing when clicking outside of it. This can be used with situations where you want to force the user to choose an option such as a cookie notice or when taking a survey.
100%
Loading...
<div style="min-height: 304px">
<twig:Modal id="delete_account">
<twig:Modal:Trigger>
<twig:Button variant="outline" {{ ...modal_trigger_attrs }}>Open Modal</twig:Button>
</twig:Modal:Trigger>
<twig:Modal:Content class="max-w-[600px]" backdrop="static">
<twig:Modal:Header>
<twig:Modal:Title>Edit profile</twig:Modal:Title>
</twig:Modal:Header>
<twig:Modal:Body>
<p class="leading-relaxed text-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean placerat, velit sit amet interdum auctor, ligula lorem posuere urna, ut lobortis odio odio et leo. Nam scelerisque vel sem vel pulvinar.
</p>
</twig:Modal:Body>
<twig:Modal:Footer>
<twig:Button type="submit">I accept</twig:Button>
<twig:Modal:Close>
<twig:Button variant="outline" {{ ...modal_close_attrs }}>Decline</twig:Button>
</twig:Modal:Close>
</twig:Modal:Footer>
</twig:Modal:Content>
</twig:Modal>
</div>
Pop-up modal
You can use this modal example to show a pop-up decision dialog to your users especially when deleting an item and making sure if the user really wants to do that by double confirming.
100%
Loading...
<div style="min-height: 304px">
<twig:Modal id="share_link">
<twig:Modal:Trigger>
<twig:Button variant="outline-danger" {{ ...modal_trigger_attrs }}>Delete</twig:Button>
</twig:Modal:Trigger>
<twig:Modal:Content class="sm:max-w-md" :showCloseButton="false">
<twig:Modal:Body class="text-center">
<twig:ux:icon name="flowbite:exclamation-circle-outline" class="mx-auto size-12 text-fg-disabled" aria-hidden="true" />
<twig:Modal:Title>Are you sure you want to delete this product from your account?</twig:Modal:Title>
<div class="flex items-center space-x-4 justify-center">
<twig:Button variant="danger">
Yes, I'm sure
</twig:Button>
<twig:Modal:Close>
<twig:Button variant="outline" {{ ...modal_close_attrs }}>
No, cancel
</twig:Button>
</twig:Modal:Close>
</div>
</twig:Modal:Body>
</twig:Modal:Content>
</twig:Modal>
</div>
Opened by default
100%
Loading...
<div style="min-height: 304px">
<twig:Modal id="delete_account" open>
<twig:Modal:Trigger>
<twig:Button variant="outline" {{ ...modal_trigger_attrs }}>Open Modal</twig:Button>
</twig:Modal:Trigger>
<twig:Modal:Content>
<twig:Modal:Header>
<twig:Modal:Title>Edit profile</twig:Modal:Title>
</twig:Modal:Header>
<twig:Modal:Body>
<p class="leading-relaxed text-body">
With less than a month to go before the European Union enacts new consumer privacy laws for its citizens, companies around the world are updating their terms of service agreements to comply.
</p>
<p class="leading-relaxed text-body">
The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is meant to ensure a common set of data rights in the European Union. It requires organizations to notify users as soon as possible of high-risk data breaches that could personally affect them.
</p>
</twig:Modal:Body>
<twig:Modal:Footer>
<twig:Button type="submit">I accept</twig:Button>
<twig:Modal:Close>
<twig:Button variant="outline" {{ ...modal_close_attrs }}>Decline</twig:Button>
</twig:Modal:Close>
</twig:Modal:Footer>
</twig:Modal:Content>
</twig:Modal>
</div>
API Reference
<twig:Modal>
| Prop | Type | Default |
|---|---|---|
open Whether the modal is open on initial render.
|
boolean |
false |
id Unique identifier used to generate internal Modal IDs.
|
string |
- |
| Block | Description |
|---|---|
content |
The modal structure, typically includes Modal:Trigger and Modal:Content. |
<twig:Modal:Body>
| Block | Description |
|---|---|
content |
The main content area of the moda. |
<twig:Modal:Close>
| Block | Description |
|---|---|
content |
The close trigger element (e.g., a Button) that closes the modal when clicked. |
<twig:Modal:Content>
| Prop | Type | Default |
|---|---|---|
showCloseButton Whether to display the close button in the top-right corner.
|
boolean |
true |
backdrop To prevent the modal from closing when clicking outside.
|
'dynamic'|'static' |
'dynamic' |
| Block | Description |
|---|---|
content |
The modal content, typically includes Modal:Header and optionally Modal:Footer. |
<twig:Modal:Footer>
| Block | Description |
|---|---|
content |
The footer area, typically contains action buttons. |
<twig:Modal:Header>
| Block | Description |
|---|---|
content |
The header area, typically contains Modal:Title and Modal:Description. |
<twig:Modal:Title>
| Block | Description |
|---|---|
content |
The title text of the modal. |
<twig:Modal:Trigger>
| Block | Description |
|---|---|
content |
The trigger element (e.g., a Button) that opens the modal when clicked. |