View as Markdown

Carousel

Cycle through images, text, and other content with optional controls and indicators.

100%
Loading...
<twig:Carousel id="demo-carousel" indicators class="w-100">
    <twig:block name="indicators">
        <button type="button" data-bs-target="#demo-carousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>
        <button type="button" data-bs-target="#demo-carousel" data-bs-slide-to="1" aria-label="Slide 2"></button>
        <button type="button" data-bs-target="#demo-carousel" data-bs-slide-to="2" aria-label="Slide 3"></button>
    </twig:block>
    <twig:Carousel:Item active>
        <div class="d-flex align-items-center justify-content-center bg-primary text-white fs-1" style="height: 12rem">First slide</div>
    </twig:Carousel:Item>
    <twig:Carousel:Item>
        <div class="d-flex align-items-center justify-content-center bg-success text-white fs-1" style="height: 12rem">Second slide</div>
    </twig:Carousel:Item>
    <twig:Carousel:Item>
        <div class="d-flex align-items-center justify-content-center bg-danger text-white fs-1" style="height: 12rem">Third slide</div>
    </twig:Carousel:Item>
</twig:Carousel>

Installation

php bin/console ux:install carousel --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 id string The unique identifier targeted by carousel controls and indicators. #}
{# @prop controls boolean Whether to render previous and next controls. #}
{# @prop indicators boolean Whether to render the indicators block. #}
{# @prop ride &#039;carousel&#039;|&#039;true&#039;|null Whether and when the carousel starts cycling automatically. #}
{# @prop interval number|null The delay in milliseconds between automatically cycling slides. #}
{# @prop pause &#039;hover&#039;|false|null Whether cycling pauses when the pointer enters the carousel. #}
{# @prop wrap boolean Whether the carousel cycles continuously. #}
{# @prop touch boolean Whether touch swipe interactions are enabled. #}
{# @prop fade boolean Whether slides crossfade instead of moving horizontally. #}
{# @prop theme &#039;light&#039;|&#039;dark&#039;|null The component-specific Bootstrap color mode. #}
{# @block indicators The carousel indicator buttons. #}
{# @block content The carousel items, typically `Carousel:Item` components. #}
{# @block previous The visually hidden label for the previous control. #}
{# @block next The visually hidden label for the next control. #}
{%- props
    id,
    controls = true,
    indicators = false,
    ride = null,
    interval = null,
    pause = null,
    wrap = true,
    touch = true,
    fade = false,
    theme = null
-%}
{%- set classes = html_classes({
    carousel: true,
    slide: true,
    &#039;carousel-fade&#039;: fade,
}) -%}
{%- set default_attrs = {id: id, class: classes} -%}
{%- if ride is not null -%}
    {%- set default_attrs = default_attrs|merge({&#039;data-bs-ride&#039;: ride}) -%}
{%- endif -%}
{%- if interval is not null -%}
    {%- set default_attrs = default_attrs|merge({&#039;data-bs-interval&#039;: interval}) -%}
{%- endif -%}
{%- if pause is not null -%}
    {%- set default_attrs = default_attrs|merge({&#039;data-bs-pause&#039;: pause is same as(false) ? &#039;false&#039; : pause}) -%}
{%- endif -%}
{%- if not wrap -%}
    {%- set default_attrs = default_attrs|merge({&#039;data-bs-wrap&#039;: &#039;false&#039;}) -%}
{%- endif -%}
{%- if not touch -%}
    {%- set default_attrs = default_attrs|merge({&#039;data-bs-touch&#039;: &#039;false&#039;}) -%}
{%- endif -%}
{%- if theme is not null -%}
    {%- set default_attrs = default_attrs|merge({&#039;data-bs-theme&#039;: theme}) -%}
{%- endif -%}

&lt;div {{ attributes.defaults(default_attrs) }}&gt;
    {%- if indicators %}
        &lt;div class=&quot;carousel-indicators&quot;&gt;
            {%- block indicators %}{% endblock -%}
        &lt;/div&gt;
    {%- endif %}
    &lt;div class=&quot;carousel-inner&quot;&gt;
        {%- block content %}{% endblock -%}
    &lt;/div&gt;
    {%- if controls %}
        &lt;button class=&quot;carousel-control-prev&quot; type=&quot;button&quot; data-bs-target=&quot;#{{ id }}&quot; data-bs-slide=&quot;prev&quot;&gt;
            &lt;span class=&quot;carousel-control-prev-icon&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;
            &lt;span class=&quot;visually-hidden&quot;&gt;{%- block previous %}Previous{% endblock -%}&lt;/span&gt;
        &lt;/button&gt;
        &lt;button class=&quot;carousel-control-next&quot; type=&quot;button&quot; data-bs-target=&quot;#{{ id }}&quot; data-bs-slide=&quot;next&quot;&gt;
            &lt;span class=&quot;carousel-control-next-icon&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;
            &lt;span class=&quot;visually-hidden&quot;&gt;{%- block next %}Next{% endblock -%}&lt;/span&gt;
        &lt;/button&gt;
    {%- endif %}
&lt;/div&gt;
{# @prop active boolean Whether this item is the currently visible slide. #}
{# @prop interval number|null The item-specific delay in milliseconds before advancing. #}
{# @block content The slide content. #}
{%- props active = false, interval = null -%}
{%- set default_attrs = {
    class: html_classes({&#039;carousel-item&#039;: true, active: active}),
} -%}
{%- if interval is not null -%}
    {%- set default_attrs = default_attrs|merge({&#039;data-bs-interval&#039;: interval}) -%}
{%- endif -%}
&lt;div {{ attributes.defaults(default_attrs) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/div&gt;

Usage

<twig:Carousel id="featured-content">
    <twig:Carousel:Item active>
        <img src="..." class="d-block w-100" alt="...">
    </twig:Carousel:Item>
    <twig:Carousel:Item>
        <img src="..." class="d-block w-100" alt="...">
    </twig:Carousel:Item>
</twig:Carousel>

Accessibility

Every carousel needs a unique id, and every indicator and control must target that same value. Mark exactly one initial Carousel:Item as active; otherwise the carousel is not visible.

Carousels that start automatically can be difficult for people using assistive technology or who need more time to read. Prefer user-controlled playback, or provide a pause control when autoplay is required. Write meaningful alternative text for images and keep the previous and next labels understandable in the page language.

Examples

Basic example

Cycle through three slides with Bootstrap's previous and next controls.

100%
Loading...
<twig:Carousel id="basic-carousel" class="w-100">
    <twig:Carousel:Item active>
        <div class="d-flex align-items-center justify-content-center bg-primary text-white fs-1" style="height: 12rem">First slide</div>
    </twig:Carousel:Item>
    <twig:Carousel:Item>
        <div class="d-flex align-items-center justify-content-center bg-secondary text-white fs-1" style="height: 12rem">Second slide</div>
    </twig:Carousel:Item>
    <twig:Carousel:Item>
        <div class="d-flex align-items-center justify-content-center bg-dark text-white fs-1" style="height: 12rem">Third slide</div>
    </twig:Carousel:Item>
</twig:Carousel>

Indicators

Add buttons that identify and select each slide.

100%
Loading...
<twig:Carousel id="indicators-carousel" indicators class="w-100">
    <twig:block name="indicators">
        <button type="button" data-bs-target="#indicators-carousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>
        <button type="button" data-bs-target="#indicators-carousel" data-bs-slide-to="1" aria-label="Slide 2"></button>
        <button type="button" data-bs-target="#indicators-carousel" data-bs-slide-to="2" aria-label="Slide 3"></button>
    </twig:block>
    <twig:Carousel:Item active><div class="bg-primary text-white p-5 text-center fs-2">First slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-success text-white p-5 text-center fs-2">Second slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-danger text-white p-5 text-center fs-2">Third slide</div></twig:Carousel:Item>
</twig:Carousel>

Captions

Overlay responsive headings and supporting text on each slide.

100%
Loading...
<twig:Carousel id="captions-carousel" indicators class="w-100">
    <twig:block name="indicators">
        <button type="button" data-bs-target="#captions-carousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>
        <button type="button" data-bs-target="#captions-carousel" data-bs-slide-to="1" aria-label="Slide 2"></button>
        <button type="button" data-bs-target="#captions-carousel" data-bs-slide-to="2" aria-label="Slide 3"></button>
    </twig:block>
    <twig:Carousel:Item active>
        <div class="bg-primary" style="height: 12rem"></div>
        <div class="carousel-caption d-none d-md-block"><h5>First slide label</h5><p>Representative placeholder content for the first slide.</p></div>
    </twig:Carousel:Item>
    <twig:Carousel:Item>
        <div class="bg-success" style="height: 12rem"></div>
        <div class="carousel-caption d-none d-md-block"><h5>Second slide label</h5><p>Representative placeholder content for the second slide.</p></div>
    </twig:Carousel:Item>
    <twig:Carousel:Item>
        <div class="bg-danger" style="height: 12rem"></div>
        <div class="carousel-caption d-none d-md-block"><h5>Third slide label</h5><p>Representative placeholder content for the third slide.</p></div>
    </twig:Carousel:Item>
</twig:Carousel>

Crossfade

Replace the horizontal movement with a fade transition.

100%
Loading...
<twig:Carousel id="crossfade-carousel" fade class="w-100">
    <twig:Carousel:Item active><div class="bg-primary text-white p-5 text-center fs-2">First slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-success text-white p-5 text-center fs-2">Second slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-danger text-white p-5 text-center fs-2">Third slide</div></twig:Carousel:Item>
</twig:Carousel>

Autoplaying carousels

Start cycling as soon as the page loads with ride="carousel".

100%
Loading...
<twig:Carousel id="autoplay-carousel" ride="carousel" class="w-100">
    <twig:Carousel:Item active><div class="bg-primary text-white p-5 text-center fs-2">First slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-success text-white p-5 text-center fs-2">Second slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-danger text-white p-5 text-center fs-2">Third slide</div></twig:Carousel:Item>
</twig:Carousel>

Ride after interaction

Start cycling only after the user first interacts with the carousel.

100%
Loading...
<twig:Carousel id="interaction-carousel" ride="true" class="w-100">
    <twig:Carousel:Item active><div class="bg-primary text-white p-5 text-center fs-2">First slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-success text-white p-5 text-center fs-2">Second slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-danger text-white p-5 text-center fs-2">Third slide</div></twig:Carousel:Item>
</twig:Carousel>

Individual item interval

Give individual slides a longer or shorter display interval.

100%
Loading...
<twig:Carousel id="interval-carousel" ride="carousel" class="w-100">
    <twig:Carousel:Item active :interval="10000"><div class="bg-primary text-white p-5 text-center fs-2">10 seconds</div></twig:Carousel:Item>
    <twig:Carousel:Item :interval="2000"><div class="bg-success text-white p-5 text-center fs-2">2 seconds</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-danger text-white p-5 text-center fs-2">Default interval</div></twig:Carousel:Item>
</twig:Carousel>

Autoplay without controls

Autoplay a carousel without rendering previous and next buttons.

100%
Loading...
<twig:Carousel id="uncontrolled-carousel" ride="carousel" :controls="false" class="w-100">
    <twig:Carousel:Item active><div class="bg-primary text-white p-5 text-center fs-2">First slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-success text-white p-5 text-center fs-2">Second slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-danger text-white p-5 text-center fs-2">Third slide</div></twig:Carousel:Item>
</twig:Carousel>

Disable touch swiping

Keep controls active while disabling touch gestures.

100%
Loading...
<twig:Carousel id="no-touch-carousel" :touch="false" class="w-100">
    <twig:Carousel:Item active><div class="bg-primary text-white p-5 text-center fs-2">First slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-success text-white p-5 text-center fs-2">Second slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-danger text-white p-5 text-center fs-2">Third slide</div></twig:Carousel:Item>
</twig:Carousel>

Dark variant

Use Bootstrap's dark color mode for controls and indicators on a light slide.

100%
Loading...
<twig:Carousel id="dark-carousel" theme="dark" indicators class="w-100">
    <twig:block name="indicators">
        <button type="button" data-bs-target="#dark-carousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>
        <button type="button" data-bs-target="#dark-carousel" data-bs-slide-to="1" aria-label="Slide 2"></button>
    </twig:block>
    <twig:Carousel:Item active><div class="bg-light text-dark p-5 text-center fs-2">First slide</div></twig:Carousel:Item>
    <twig:Carousel:Item><div class="bg-light text-dark p-5 text-center fs-2">Second slide</div></twig:Carousel:Item>
</twig:Carousel>

API Reference

<twig:Carousel>

Prop Type Default
id 
string -
controls 
boolean true
indicators 
boolean false
ride 
'carousel'|'true'|null null
interval 
number|null null
pause 
'hover'|false|null null
wrap 
boolean true
touch 
boolean true
fade 
boolean false
theme 
'light'|'dark'|null null
Block Description
indicators The carousel indicator buttons.
content The carousel items, typically Carousel:Item components.
previous The visually hidden label for the previous control.
next The visually hidden label for the next control.

<twig:Carousel:Item>

Prop Type Default
active 
boolean false
interval 
number|null null
Block Description
content The slide content.