View as Markdown

Spinner

Indicate the loading state of a component or page with Bootstrap spinners.

100%
Loading...
<div class="d-flex align-items-center gap-3">
    <twig:Spinner color="primary" label="Loading..." />
    <twig:Spinner type="grow" color="secondary" label="Loading..." />
</div>

Installation

php bin/console ux:install spinner --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 type &#039;border&#039;|&#039;grow&#039; The spinner animation style. #}
{# @prop color &#039;primary&#039;|&#039;secondary&#039;|&#039;success&#039;|&#039;danger&#039;|&#039;warning&#039;|&#039;info&#039;|&#039;light&#039;|&#039;dark&#039;|null The Bootstrap contextual text color. #}
{# @prop small boolean Whether to use Bootstrap&#039;s compact spinner size. #}
{# @prop role string|null The ARIA role used for an announced loading status. #}
{# @prop label string The visually hidden loading message. #}
{# @prop decorative boolean Whether to hide the spinner from assistive technologies. #}
{# @block content The accessible loading message or custom inner content. #}
{%- props
    type = &#039;border&#039;,
    color = null,
    small = false,
    role = &#039;status&#039;,
    label = &#039;Loading...&#039;,
    decorative = false
-%}

{%- set spinner_type = type == &#039;grow&#039; ? &#039;grow&#039; : &#039;border&#039; -%}
{%- set spinner_classes = html_classes({
    (&#039;spinner-&#039; ~ spinner_type): true,
    (&#039;spinner-&#039; ~ spinner_type ~ &#039;-sm&#039;): small,
    (&#039;text-&#039; ~ color): color in [&#039;primary&#039;, &#039;secondary&#039;, &#039;success&#039;, &#039;danger&#039;, &#039;warning&#039;, &#039;info&#039;, &#039;light&#039;, &#039;dark&#039;],
}) -%}
{%- set default_attrs = {class: spinner_classes} -%}
{%- if decorative -%}
    {%- set default_attrs = default_attrs|merge({&#039;aria-hidden&#039;: &#039;true&#039;}) -%}
{%- elseif role not in [null, &#039;&#039;] -%}
    {%- set default_attrs = default_attrs|merge({role: role}) -%}
{%- endif -%}

&lt;div {{ attributes.defaults(default_attrs) }}&gt;
    {%- block content -%}
        {%- if not decorative -%}
            &lt;span class=&quot;visually-hidden&quot;&gt;{{ label }}&lt;/span&gt;
        {%- endif -%}
    {%- endblock -%}
&lt;/div&gt;

Usage

<twig:Spinner label="Loading search results..." />

Accessibility

Standalone spinners use role="status" and include a visually hidden loading message. Customize label so the announced status describes what is loading.

When visible text already announces the status, set decorative to hide the spinner itself from assistive technologies.

Examples

Border spinner

Use the border animation for a lightweight loading indicator.

100%
Loading...
<twig:Spinner label="Loading..." />

Colors

The spinner inherits currentColor; use Bootstrap contextual text colors to customize it.

100%
Loading...
<div class="d-flex flex-wrap gap-3">
    <twig:Spinner color="primary" label="Loading..." />
    <twig:Spinner color="secondary" label="Loading..." />
    <twig:Spinner color="success" label="Loading..." />
    <twig:Spinner color="danger" label="Loading..." />
    <twig:Spinner color="warning" label="Loading..." />
    <twig:Spinner color="info" label="Loading..." />
    <twig:Spinner color="light" label="Loading..." />
    <twig:Spinner color="dark" label="Loading..." />
</div>

Growing spinner

Switch to the growing animation while keeping the same contextual color options.

100%
Loading...
<div class="d-flex flex-wrap gap-3">
    <twig:Spinner type="grow" label="Loading..." />
    <twig:Spinner type="grow" color="primary" label="Loading..." />
    <twig:Spinner type="grow" color="secondary" label="Loading..." />
    <twig:Spinner type="grow" color="success" label="Loading..." />
    <twig:Spinner type="grow" color="danger" label="Loading..." />
    <twig:Spinner type="grow" color="warning" label="Loading..." />
    <twig:Spinner type="grow" color="info" label="Loading..." />
    <twig:Spinner type="grow" color="light" label="Loading..." />
    <twig:Spinner type="grow" color="dark" label="Loading..." />
</div>

Margin

Use Bootstrap spacing utilities to add space around a spinner.

100%
Loading...
<twig:Spinner class="m-5" label="Loading..." />

Flex placement

Use flexbox utilities to center a spinner or place it after a visible loading status.

100%
Loading...
<div class="d-flex flex-column gap-4">
    <div class="d-flex justify-content-center">
        <twig:Spinner label="Loading..." />
    </div>

    <div class="d-flex align-items-center">
        <strong role="status">Loading...</strong>
        <twig:Spinner class="ms-auto" decorative />
    </div>
</div>

Floats

Use float utilities when a spinner needs to follow the edge of its container.

100%
Loading...
<div class="clearfix">
    <twig:Spinner class="float-end" label="Loading..." />
</div>

Text align

Because the component is inline-flex, text alignment utilities can position it within a block.

100%
Loading...
<div class="text-center">
    <twig:Spinner label="Loading..." />
</div>

Size

Use the compact Bootstrap size or set custom dimensions with CSS.

100%
Loading...
<div class="d-flex align-items-center gap-3">
    <twig:Spinner small label="Loading..." />
    <twig:Spinner type="grow" small label="Loading..." />
    <twig:Spinner style="width: 3rem; height: 3rem" label="Loading..." />
    <twig:Spinner type="grow" style="width: 3rem; height: 3rem" label="Loading..." />
</div>

Buttons

Place a decorative spinner inside a disabled button and provide the loading status as button text or visually hidden text.

100%
Loading...
<div class="d-flex flex-wrap gap-2">
    <button class="btn btn-primary" type="button" disabled>
        <twig:Spinner small decorative />
        <span class="visually-hidden">Loading...</span>
    </button>
    <button class="btn btn-primary" type="button" disabled>
        <twig:Spinner small decorative />
        <span>Loading...</span>
    </button>
    <button class="btn btn-primary" type="button" disabled>
        <twig:Spinner type="grow" small decorative />
        <span class="visually-hidden">Loading...</span>
    </button>
    <button class="btn btn-primary" type="button" disabled>
        <twig:Spinner type="grow" small decorative />
        <span>Loading...</span>
    </button>
</div>

API Reference

<twig:Spinner>

Prop Type Default
type 
'border'|'grow' 'border'
color 
'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark'|null null
small 
boolean false
role 
string|null 'status'
label 
string 'Loading...'
decorative 
boolean false
Block Description
content The accessible loading message or custom inner content.