View as Markdown

Toast

Show lightweight Bootstrap notifications with optional headers and dismissal controls.

100%
Loading...
<div class="d-flex justify-content-center p-4">
    <twig:Toast :autohide="false">
        <twig:Toast:Header timestamp="11 mins ago">
            <span class="rounded me-2 bg-primary" style="width: 20px; height: 20px"></span>
            <strong class="me-auto">Bootstrap</strong>
        </twig:Toast:Header>
        <twig:Toast:Body>
            Hello, world! This is a toast message.
        </twig:Toast:Body>
    </twig:Toast>
</div>

Installation

php bin/console ux:install toast --kit bootstrap

Install the following Composer dependencies:

composer require twig/extra-bundle twig/html-extra:^3.12.0

Install the following front-end dependencies:

npm install bootstrap@^5.3.0
# ...or with Symfony AssetMapper
php bin/console importmap:require bootstrap bootstrap/dist/css/bootstrap.min.css

Copy the following file(s) into your app:

{# @prop show boolean Whether the toast is visible on initial render. #}
{# @prop autohide boolean Whether Bootstrap hides the toast automatically. #}
{# @prop delay int|null The delay in milliseconds before an autohiding toast closes. #}
{# @prop animation boolean Whether Bootstrap animates the toast. #}
{# @prop role &#039;status&#039;|&#039;alert&#039; The accessibility role that describes the notification urgency. #}
{# @prop live &#039;polite&#039;|&#039;assertive&#039; The live region politeness level. #}
{# @block content The toast structure, typically `Toast:Header` and `Toast:Body`. #}
{%- props
    show = true,
    autohide = true,
    delay = null,
    animation = true,
    role = &#039;status&#039;,
    live = &#039;polite&#039;
-%}
{%- set final_role = role in [&#039;status&#039;, &#039;alert&#039;] ? role : &#039;status&#039; -%}
{%- set final_live = live in [&#039;polite&#039;, &#039;assertive&#039;] ? live : &#039;polite&#039; -%}
{%- set toast_classes = html_classes({
    toast: true,
    show: show,
}) -%}
{%- set default_attrs = {
    class: toast_classes,
    role: final_role,
    &#039;aria-live&#039;: final_live,
    &#039;aria-atomic&#039;: &#039;true&#039;,
} -%}
{%- if autohide and delay is not null -%}
    {%- set default_attrs = default_attrs|merge({&#039;data-bs-delay&#039;: delay}) -%}
{%- endif -%}
{%- if not autohide -%}
    {%- set default_attrs = default_attrs|merge({&#039;data-bs-autohide&#039;: &#039;false&#039;}) -%}
{%- endif -%}
{%- if not animation -%}
    {%- set default_attrs = default_attrs|merge({&#039;data-bs-animation&#039;: &#039;false&#039;}) -%}
{%- endif -%}
&lt;div {{ attributes.defaults(default_attrs) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/div&gt;
{# @block content The notification message and optional actions. #}
&lt;div {{ attributes.defaults({class: &#039;toast-body&#039;}) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/div&gt;
{# @prop title string The notification title. #}
{# @prop timestamp string|null The optional relative or absolute notification time. #}
{# @prop imageSrc string|null The optional image displayed before the title. #}
{# @prop imageAlt string The alternative text for the header image. #}
{# @prop closeButton boolean Whether to render a Bootstrap dismiss button. #}
{# @prop closeLabel string The accessible label of the dismiss button. #}
{# @block content The header title or custom header content. #}
{%- props
    title = &#039;&#039;,
    timestamp = null,
    imageSrc = null,
    imageAlt = &#039;&#039;,
    closeButton = true,
    closeLabel = &#039;Close&#039;
-%}
&lt;div {{ attributes.defaults({class: &#039;toast-header&#039;}) }}&gt;
    {%- if imageSrc is not null -%}
        &lt;img src=&quot;{{ imageSrc }}&quot; class=&quot;rounded me-2&quot; alt=&quot;{{ imageAlt }}&quot;&gt;
    {%- endif -%}
    {%- block content -%}
        &lt;strong class=&quot;me-auto&quot;&gt;{{ title }}&lt;/strong&gt;
    {%- endblock -%}
    {%- if timestamp is not null -%}
        &lt;small class=&quot;text-body-secondary&quot;&gt;{{ timestamp }}&lt;/small&gt;
    {%- endif -%}
    {%- if closeButton -%}
        &lt;button type=&quot;button&quot; class=&quot;btn-close&quot; data-bs-dismiss=&quot;toast&quot; aria-label=&quot;{{ closeLabel }}&quot;&gt;&lt;/button&gt;
    {%- endif -%}
&lt;/div&gt;

Usage

<twig:Toast :autohide="false">
    <twig:Toast:Header title="Bootstrap" timestamp="just now" />
    <twig:Toast:Body>
        Your changes have been saved.
    </twig:Toast:Body>
</twig:Toast>

Bootstrap toasts are opt-in. Create an instance before showing a hidden toast:

import { Toast } from 'bootstrap';

const trigger = document.querySelector('#live-toast-trigger');
const element = document.querySelector('#live-toast');
const toast = Toast.getOrCreateInstance(element);

trigger.addEventListener('click', () => toast.show());

A toast rendered with show is immediately visible and does not need JavaScript until it must hide, dismiss, or be shown again.

Accessibility

Keep the live region in the DOM before injecting or updating a toast. Use the defaults role="status" and live="polite" for ordinary notifications; reserve role="alert" and live="assertive" for urgent interruptions.

Choose a delay long enough to read the message. Set autohide to false when a toast contains interactive controls and always provide an explicit close action in that case. Avoid placing important actions inside an autohiding toast because focus may be lost when it disappears.

Examples

Basic

Compose a standard toast from a header and body.

100%
Loading...
<twig:Toast :autohide="false">
    <twig:Toast:Header timestamp="11 mins ago">
        <span class="rounded me-2 bg-primary" style="width: 20px; height: 20px"></span>
        <strong class="me-auto">Bootstrap</strong>
    </twig:Toast:Header>
    <twig:Toast:Body>
        Hello, world! This is a toast message.
    </twig:Toast:Body>
</twig:Toast>

Live example

Start hidden and show the toast from application JavaScript.

100%
Loading...
<button type="button" class="btn btn-primary mb-3" id="live-toast-trigger">
    Show live toast
</button>

<twig:Toast id="live-toast" :show="false">
    <twig:Toast:Header title="Bootstrap" timestamp="just now" />
    <twig:Toast:Body>
        See? Just like this.
    </twig:Toast:Body>
</twig:Toast>

Translucent

Place the default translucent toast over colored content.

100%
Loading...
<div class="p-4 rounded bg-primary bg-gradient">
    <twig:Toast :autohide="false">
        <twig:Toast:Header title="Bootstrap" timestamp="11 mins ago" />
        <twig:Toast:Body>
            Toasts are slightly translucent to blend with what is behind them.
        </twig:Toast:Body>
    </twig:Toast>
</div>

Stacking

Stack multiple notifications inside a Bootstrap toast container.

100%
Loading...
<div class="toast-container position-static">
    <twig:Toast :autohide="false">
        <twig:Toast:Header title="Bootstrap" timestamp="just now" />
        <twig:Toast:Body>
            First toast in the stack.
        </twig:Toast:Body>
    </twig:Toast>

    <twig:Toast :autohide="false">
        <twig:Toast:Header title="Bootstrap" timestamp="2 seconds ago" />
        <twig:Toast:Body>
            Second toast, automatically spaced by the container.
        </twig:Toast:Body>
    </twig:Toast>
</div>

Custom content

Omit the header and compose body content, actions, and dismissal controls directly.

100%
Loading...
<div class="d-flex flex-column gap-3">
    <twig:Toast :autohide="false" class="align-items-center">
        <div class="d-flex">
            <twig:Toast:Body>
                Hello, world! This is a toast message.
            </twig:Toast:Body>
            <button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
        </div>
    </twig:Toast>

    <twig:Toast :autohide="false">
        <twig:Toast:Body>
            Hello, world! This is a toast message.
            <div class="mt-2 pt-2 border-top">
                <button type="button" class="btn btn-primary btn-sm">Take action</button>
                <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="toast">Close</button>
            </div>
        </twig:Toast:Body>
    </twig:Toast>
</div>

Color schemes

Combine contextual background and foreground utilities for stronger visual emphasis.

100%
Loading...
<div class="d-flex flex-column gap-3">
    {% for color in ['primary', 'success', 'danger'] %}
        <twig:Toast :autohide="false" class="text-bg-{{ color }} border-0">
            <div class="d-flex">
                <twig:Toast:Body>
                    A {{ color }} toast with matching contextual color.
                </twig:Toast:Body>
                <button type="button" class="btn-close me-2 m-auto" data-bs-theme="dark" data-bs-dismiss="toast" aria-label="Close"></button>
            </div>
        </twig:Toast>
    {% endfor %}
</div>

Placement

Position a toast container with Bootstrap's positioning utilities.

100%
Loading...
<div class="position-relative w-100 bg-body-tertiary border rounded" style="height: 320px">
    <div class="toast-container position-absolute top-0 end-0 p-3">
        <twig:Toast :autohide="false">
            <twig:Toast:Header title="Bootstrap" timestamp="just now" />
            <twig:Toast:Body>
                Position the container with Bootstrap utilities.
            </twig:Toast:Body>
        </twig:Toast>
    </div>
</div>

API Reference

<twig:Toast>

Prop Type Default
show 
boolean true
autohide 
boolean true
delay 
int|null null
animation 
boolean true
role 
'status'|'alert' 'status'
live 
'polite'|'assertive' 'polite'
Block Description
content The toast structure, typically Toast:Header and Toast:Body.

<twig:Toast:Body>

Block Description
content The notification message and optional actions.

<twig:Toast:Header>

Prop Type Default
title 
string ''
timestamp 
string|null null
imageSrc 
string|null null
imageAlt 
string ''
closeButton 
boolean true
closeLabel 
string 'Close'
Block Description
content The header title or custom header content.