View as Markdown

Placeholder

Uses loading placeholders to indicate that content may still be loading.

100%
Loading...
<div class="card" style="width: 18rem;" aria-busy="true">
    <svg class="card-img-top text-secondary bg-body-secondary" width="100%" height="140" role="img" aria-label="Loading image placeholder">
        <rect width="100%" height="100%" fill="currentColor" />
    </svg>
    <div class="card-body">
        <twig:Placeholder:Animation animation="glow" tag="h5" class="card-title">
            <twig:Placeholder class="col-6" />
        </twig:Placeholder:Animation>
        <twig:Placeholder:Animation animation="glow" tag="p" class="card-text">
            <twig:Placeholder class="col-7" />
            <twig:Placeholder class="col-4" />
            <twig:Placeholder class="col-4" />
            <twig:Placeholder class="col-6" />
            <twig:Placeholder class="col-8" />
        </twig:Placeholder:Animation>
        <twig:Placeholder tag="a" class="btn btn-primary disabled col-6" aria-disabled="true" />
    </div>
</div>

Installation

php bin/console ux:install placeholder --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 tag &#039;span&#039;|&#039;a&#039;|&#039;div&#039;|&#039;p&#039;|&#039;h1&#039;|&#039;h2&#039;|&#039;h3&#039;|&#039;h4&#039;|&#039;h5&#039;|&#039;h6&#039; The HTML element used for the placeholder. #}
{# @prop size &#039;lg&#039;|&#039;sm&#039;|&#039;xs&#039;|null The Bootstrap placeholder size. #}
{%- props
    tag = &#039;span&#039;,
    size = null
-%}

{%- set final_tag = tag in [&#039;span&#039;, &#039;a&#039;, &#039;div&#039;, &#039;p&#039;, &#039;h1&#039;, &#039;h2&#039;, &#039;h3&#039;, &#039;h4&#039;, &#039;h5&#039;, &#039;h6&#039;] ? tag : &#039;span&#039; -%}
{%- set classes = html_classes({
    placeholder: true,
    (&#039;placeholder-&#039; ~ size): size in [&#039;lg&#039;, &#039;sm&#039;, &#039;xs&#039;],
}) -%}

&lt;{{ final_tag }} {{ attributes.defaults({class: classes, &#039;aria-hidden&#039;: &#039;true&#039;}) }}&gt;&lt;/{{ final_tag }}&gt;
{# @prop tag &#039;span&#039;|&#039;a&#039;|&#039;div&#039;|&#039;p&#039;|&#039;h1&#039;|&#039;h2&#039;|&#039;h3&#039;|&#039;h4&#039;|&#039;h5&#039;|&#039;h6&#039; The HTML element used for the animation wrapper. #}
{# @prop animation &#039;glow&#039;|&#039;wave&#039; The Bootstrap placeholder animation. #}
{# @block content The placeholders animated by this wrapper. #}
{%- props
    tag = &#039;div&#039;,
    animation = &#039;glow&#039;
-%}

{%- set final_tag = tag in [&#039;span&#039;, &#039;a&#039;, &#039;div&#039;, &#039;p&#039;, &#039;h1&#039;, &#039;h2&#039;, &#039;h3&#039;, &#039;h4&#039;, &#039;h5&#039;, &#039;h6&#039;] ? tag : &#039;div&#039; -%}
{%- set classes = html_classes({
    (&#039;placeholder-&#039; ~ animation): true,
}) -%}

&lt;{{ final_tag }} {{ attributes.defaults({class: classes, &#039;aria-hidden&#039;: &#039;true&#039;}) }}&gt;
    {%- block content -%}{%- endblock -%}
&lt;/{{ final_tag }}&gt;

Usage

<twig:Placeholder class="col-6" />

Accessibility

Placeholders are decorative and hidden from assistive technologies. Mark the loading region with aria-busy="true", then announce meaningful content changes when loading completes.

The application remains responsible for replacing placeholders and informing users when the updated content is available.

Examples

Match a placeholder card's proportions to the content it temporarily replaces.

100%
Loading...
<div class="row g-3">
    <div class="col-md-6">
        <div class="card h-100">
            <svg class="card-img-top text-secondary bg-body-secondary" width="100%" height="140" role="img" aria-label="Example image placeholder">
                <rect width="100%" height="100%" fill="currentColor" />
            </svg>
            <div class="card-body">
                <h5 class="card-title">Card title</h5>
                <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
                <a href="#" class="btn btn-primary">Go somewhere</a>
            </div>
        </div>
    </div>

    <div class="col-md-6">
        <div class="card h-100" aria-hidden="true">
            <svg class="card-img-top text-secondary bg-body-secondary" width="100%" height="140">
                <rect width="100%" height="100%" fill="currentColor" />
            </svg>
            <div class="card-body">
                <twig:Placeholder:Animation animation="glow" tag="h5" class="card-title">
                    <twig:Placeholder class="col-6" />
                </twig:Placeholder:Animation>
                <twig:Placeholder:Animation animation="glow" tag="p" class="card-text">
                    <twig:Placeholder class="col-7" />
                    <twig:Placeholder class="col-4" />
                    <twig:Placeholder class="col-4" />
                    <twig:Placeholder class="col-6" />
                    <twig:Placeholder class="col-8" />
                </twig:Placeholder:Animation>
                <twig:Placeholder tag="a" class="btn btn-primary disabled col-6" aria-disabled="true" />
            </div>
        </div>
    </div>
</div>

How it works

Use placeholders as text replacements or modifiers on existing components.

100%
Loading...
<p aria-hidden="true">
    <twig:Placeholder class="col-6" />
</p>

<twig:Placeholder tag="a" class="btn btn-primary disabled col-4" aria-disabled="true" />

Width

Set widths with grid columns, width utilities, or inline styles.

100%
Loading...
<div class="w-100 d-flex flex-column gap-2">
    <twig:Placeholder class="col-6" />
    <twig:Placeholder class="w-75" />
    <twig:Placeholder style="width: 25%;" />
</div>

Color

Use the inherited text color or Bootstrap background utilities.

100%
Loading...
<div class="w-100 d-flex flex-column gap-2">
    <twig:Placeholder class="col-12" />
    <twig:Placeholder class="col-12 bg-primary" />
    <twig:Placeholder class="col-12 bg-secondary" />
    <twig:Placeholder class="col-12 bg-success" />
    <twig:Placeholder class="col-12 bg-danger" />
    <twig:Placeholder class="col-12 bg-warning" />
    <twig:Placeholder class="col-12 bg-info" />
    <twig:Placeholder class="col-12 bg-light" />
    <twig:Placeholder class="col-12 bg-dark" />
</div>

Sizing

Adjust placeholder height with large, small, and extra-small variants.

100%
Loading...
<div class="w-100 d-flex flex-column gap-2">
    <twig:Placeholder size="lg" class="col-12" />
    <twig:Placeholder class="col-12" />
    <twig:Placeholder size="sm" class="col-12" />
    <twig:Placeholder size="xs" class="col-12" />
</div>

Animation

Use glow or wave animation to reinforce that content is actively loading.

100%
Loading...
<div class="w-100 d-flex flex-column gap-3">
    <twig:Placeholder:Animation animation="glow" tag="p" class="mb-0">
        <twig:Placeholder class="col-12" />
    </twig:Placeholder:Animation>

    <twig:Placeholder:Animation animation="wave" tag="p" class="mb-0">
        <twig:Placeholder class="col-12" />
    </twig:Placeholder:Animation>
</div>

API Reference

<twig:Placeholder>

Prop Type Default
tag 
'span'|'a'|'div'|'p'|'h1'|'h2'|'h3'|'h4'|'h5'|'h6' 'span'
size 
'lg'|'sm'|'xs'|null null

<twig:Placeholder:Animation>

Prop Type Default
tag 
'span'|'a'|'div'|'p'|'h1'|'h2'|'h3'|'h4'|'h5'|'h6' 'div'
animation 
'glow'|'wave' 'glow'
Block Description
content The placeholders animated by this wrapper.