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

Note

Available since UX Toolkit 3.4.

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:

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

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.