Carousel
Cycle through images, text, and other content with optional controls and indicators.
<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 'carousel'|'true'|null Whether and when the carousel starts cycling automatically. #}
{# @prop interval number|null The delay in milliseconds between automatically cycling slides. #}
{# @prop pause 'hover'|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 'light'|'dark'|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,
'carousel-fade': fade,
}) -%}
{%- set default_attrs = {id: id, class: classes} -%}
{%- if ride is not null -%}
{%- set default_attrs = default_attrs|merge({'data-bs-ride': ride}) -%}
{%- endif -%}
{%- if interval is not null -%}
{%- set default_attrs = default_attrs|merge({'data-bs-interval': interval}) -%}
{%- endif -%}
{%- if pause is not null -%}
{%- set default_attrs = default_attrs|merge({'data-bs-pause': pause is same as(false) ? 'false' : pause}) -%}
{%- endif -%}
{%- if not wrap -%}
{%- set default_attrs = default_attrs|merge({'data-bs-wrap': 'false'}) -%}
{%- endif -%}
{%- if not touch -%}
{%- set default_attrs = default_attrs|merge({'data-bs-touch': 'false'}) -%}
{%- endif -%}
{%- if theme is not null -%}
{%- set default_attrs = default_attrs|merge({'data-bs-theme': theme}) -%}
{%- endif -%}
<div {{ attributes.defaults(default_attrs) }}>
{%- if indicators %}
<div class="carousel-indicators">
{%- block indicators %}{% endblock -%}
</div>
{%- endif %}
<div class="carousel-inner">
{%- block content %}{% endblock -%}
</div>
{%- if controls %}
<button class="carousel-control-prev" type="button" data-bs-target="#{{ id }}" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">{%- block previous %}Previous{% endblock -%}</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#{{ id }}" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">{%- block next %}Next{% endblock -%}</span>
</button>
{%- endif %}
</div>
{# @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({'carousel-item': true, active: active}),
} -%}
{%- if interval is not null -%}
{%- set default_attrs = default_attrs|merge({'data-bs-interval': interval}) -%}
{%- endif -%}
<div {{ attributes.defaults(default_attrs) }}>
{%- block content %}{% endblock -%}
</div>
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.
<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.
<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.
<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.
<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".
<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.
<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.
<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.
<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.
<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.
<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 The unique identifier targeted by carousel controls and indicators.
|
string |
- |
controls Whether to render previous and next controls.
|
boolean |
true |
indicators Whether to render the indicators block.
|
boolean |
false |
ride Whether and when the carousel starts cycling automatically.
|
'carousel'|'true'|null |
null |
interval The delay in milliseconds between automatically cycling slides.
|
number|null |
null |
pause Whether cycling pauses when the pointer enters the carousel.
|
'hover'|false|null |
null |
wrap Whether the carousel cycles continuously.
|
boolean |
true |
touch Whether touch swipe interactions are enabled.
|
boolean |
true |
fade Whether slides crossfade instead of moving horizontally.
|
boolean |
false |
theme The component-specific Bootstrap color mode.
|
'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 Whether this item is the currently visible slide.
|
boolean |
false |
interval The item-specific delay in milliseconds before advancing.
|
number|null |
null |
| Block | Description |
|---|---|
content |
The slide content. |