View as Markdown

Card

Build flexible content containers with optional headers, footers, images, and contextual styles.

100%
Loading...
{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}

<twig:Card style="width: 22rem;">
    <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
    <twig:Card:Body>
        <twig:Card:Title>Explore the outdoors</twig:Card:Title>
        <twig:Card:Subtitle class="mb-2 text-body-secondary">Weekend inspiration</twig:Card:Subtitle>
        <twig:Card:Text>Discover a new trail and make time for a change of scenery.</twig:Card:Text>
        <a href="#" class="btn btn-primary">Find a trail</a>
        <twig:Card:Link href="#">View guide</twig:Card:Link>
    </twig:Card:Body>
    <twig:Card:Footer class="text-body-secondary">Updated today</twig:Card:Footer>
</twig:Card>

Installation

php bin/console ux:install card --kit bootstrap

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:

{# @block content The card content, typically including `Card:Header`, `Card:Body`, and `Card:Footer`. #}
&lt;div {{ attributes.defaults({class: &#039;card&#039;}) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/div&gt;
{# @block content The main content area of the card. #}
&lt;div {{ attributes.defaults({class: &#039;card-body&#039;}) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/div&gt;
{# @block content The footer content of the card. #}
&lt;div {{ attributes.defaults({class: &#039;card-footer&#039;}) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/div&gt;
{# @prop tag &#039;div&#039;|&#039;h2&#039;|&#039;h3&#039;|&#039;h4&#039;|&#039;h5&#039;|&#039;h6&#039; The HTML element to render. #}
{# @block content The header content of the card. #}
{%- props tag = &#039;div&#039; -%}
{%- set final_tag = tag in [&#039;h2&#039;, &#039;h3&#039;, &#039;h4&#039;, &#039;h5&#039;, &#039;h6&#039;] ? tag : &#039;div&#039; -%}
&lt;{{ final_tag }} {{ attributes.defaults({class: &#039;card-header&#039;}) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/{{ final_tag }}&gt;
{# @prop src string The image source URL. #}
{# @prop alt string A text alternative describing the image. #}
{# @prop position &#039;top&#039;|&#039;bottom&#039;|&#039;background&#039; The image position within the card. #}
{%- props src, alt = &#039;&#039;, position = &#039;top&#039; -%}
{%- set classes = {
    top: &#039;card-img-top&#039;,
    bottom: &#039;card-img-bottom&#039;,
    background: &#039;card-img&#039;,
} -%}
&lt;img {{ attributes.defaults({
    src: src,
    alt: alt,
    class: classes[position] ?? classes.top,
}) }}&gt;
{# @prop href string The link destination. #}
{# @block content The link label. #}
{%- props href = &#039;#&#039; -%}
&lt;a {{ attributes.defaults({href: href, class: &#039;card-link&#039;}) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/a&gt;
{# @block content The content displayed over the card image. #}
&lt;div {{ attributes.defaults({class: &#039;card-img-overlay&#039;}) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/div&gt;
{# @prop tag &#039;h2&#039;|&#039;h3&#039;|&#039;h4&#039;|&#039;h5&#039;|&#039;h6&#039;|&#039;div&#039; The HTML element to render. #}
{# @block content The subtitle text of the card. #}
{%- props tag = &#039;h6&#039; -%}
{%- set final_tag = tag in [&#039;h2&#039;, &#039;h3&#039;, &#039;h4&#039;, &#039;h5&#039;, &#039;h6&#039;] ? tag : &#039;div&#039; -%}
&lt;{{ final_tag }} {{ attributes.defaults({class: &#039;card-subtitle&#039;}) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/{{ final_tag }}&gt;
{# @prop tag &#039;p&#039;|&#039;div&#039; The HTML element to render. #}
{# @block content The supporting text of the card. #}
{%- props tag = &#039;p&#039; -%}
{%- set final_tag = tag == &#039;div&#039; ? &#039;div&#039; : &#039;p&#039; -%}
&lt;{{ final_tag }} {{ attributes.defaults({class: &#039;card-text&#039;}) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/{{ final_tag }}&gt;
{# @prop tag &#039;h2&#039;|&#039;h3&#039;|&#039;h4&#039;|&#039;h5&#039;|&#039;h6&#039;|&#039;div&#039; The HTML element to render. #}
{# @block content The title text of the card. #}
{%- props tag = &#039;h5&#039; -%}
{%- set final_tag = tag in [&#039;h2&#039;, &#039;h3&#039;, &#039;h4&#039;, &#039;h5&#039;, &#039;h6&#039;] ? tag : &#039;div&#039; -%}
&lt;{{ final_tag }} {{ attributes.defaults({class: &#039;card-title&#039;}) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/{{ final_tag }}&gt;

Usage

<twig:Card style="max-width: 24rem;">
    <twig:Card:Header>Featured</twig:Card:Header>
    <twig:Card:Body>
        <twig:Card:Title>Card title</twig:Card:Title>
        <twig:Card:Text>Some quick example text to build on the card title.</twig:Card:Text>
    </twig:Card:Body>
    <twig:Card:Footer class="text-body-secondary">2 days ago</twig:Card:Footer>
</twig:Card>

Accessibility

Choose a semantic heading level for each card title that fits the surrounding page hierarchy.

Do not rely on background, text, or border color alone to communicate meaning. Keep sufficient contrast and provide an equivalent text cue when color carries information.

Examples

Combine an image, title, supporting text, and action in a fixed-width card.

100%
Loading...
{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}

<twig:Card style="width: 18rem;">
    <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
    <twig:Card:Body>
        <twig:Card:Title>Card title</twig:Card:Title>
        <twig:Card:Text>Some quick example text to build on the card title and make up the bulk of the card's content.</twig:Card:Text>
        <a href="#" class="btn btn-primary">Go somewhere</a>
    </twig:Card:Body>
</twig:Card>

Content types

Mix card bodies, titles, subtitles, text, links, images, and list groups.

100%
Loading...
{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}

<div class="d-flex flex-wrap gap-3">
    <twig:Card style="width: 18rem;">
        <twig:Card:Body>
            <twig:Card:Title>Card title</twig:Card:Title>
            <twig:Card:Subtitle class="mb-2 text-body-secondary">Card subtitle</twig:Card:Subtitle>
            <twig:Card:Text>Some quick example text to build on the card title.</twig:Card:Text>
            <twig:Card:Link href="#">Card link</twig:Card:Link>
            <twig:Card:Link href="#">Another link</twig:Card:Link>
        </twig:Card:Body>
    </twig:Card>

    <twig:Card style="width: 18rem;">
        <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
        <twig:Card:Body>
            <twig:Card:Text>Text, images, list groups, and links can be freely combined.</twig:Card:Text>
        </twig:Card:Body>
        <ul class="list-group list-group-flush">
            <li class="list-group-item">An item</li>
            <li class="list-group-item">A second item</li>
            <li class="list-group-item">A third item</li>
        </ul>
        <twig:Card:Body>
            <twig:Card:Link href="#">Card link</twig:Card:Link>
        </twig:Card:Body>
    </twig:Card>
</div>

Add optional headers and footers, including semantic heading and quote content.

100%
Loading...
<div class="d-grid gap-3">
    <twig:Card>
        <twig:Card:Header>Featured</twig:Card:Header>
        <twig:Card:Body>
            <twig:Card:Title>Special title treatment</twig:Card:Title>
            <twig:Card:Text>With supporting text below as a natural lead-in to additional content.</twig:Card:Text>
            <a href="#" class="btn btn-primary">Go somewhere</a>
        </twig:Card:Body>
    </twig:Card>

    <twig:Card class="text-center">
        <twig:Card:Header tag="h5">Featured</twig:Card:Header>
        <twig:Card:Body>
            <figure class="mb-0">
                <blockquote class="blockquote"><p>A well-known quote, contained in a blockquote element.</p></blockquote>
                <figcaption class="blockquote-footer mb-0">Someone famous in <cite title="Source Title">Source Title</cite></figcaption>
            </figure>
        </twig:Card:Body>
        <twig:Card:Footer class="text-body-secondary">2 days ago</twig:Card:Footer>
    </twig:Card>
</div>

Sizing

Size cards with Bootstrap's grid, width utilities, or custom CSS.

100%
Loading...
<div class="row">
    <div class="col-sm-6 mb-3 mb-sm-0">
        <twig:Card>
            <twig:Card:Body>
                <twig:Card:Title>Grid-sized card</twig:Card:Title>
                <twig:Card:Text>The surrounding Bootstrap grid controls this card's width.</twig:Card:Text>
            </twig:Card:Body>
        </twig:Card>
    </div>
    <div class="col-sm-6">
        <twig:Card class="w-75">
            <twig:Card:Body>
                <twig:Card:Title>Utility-sized card</twig:Card:Title>
                <twig:Card:Text>Width utilities or custom CSS can size a card directly.</twig:Card:Text>
            </twig:Card:Body>
        </twig:Card>
    </div>
</div>

Text alignment

Apply Bootstrap text alignment utilities to a card or one of its sections.

100%
Loading...
<div class="d-flex flex-wrap gap-3">
    {% for alignment, label in {'': 'Start', 'text-center': 'Center', 'text-end': 'End'} %}
        <twig:Card class="{{ alignment }}" style="width: 18rem;">
            <twig:Card:Body>
                <twig:Card:Title>{{ label }} aligned</twig:Card:Title>
                <twig:Card:Text>Use Bootstrap text utilities on the whole card or an individual section.</twig:Card:Text>
                <a href="#" class="btn btn-primary">Go somewhere</a>
            </twig:Card:Body>
        </twig:Card>
    {% endfor %}
</div>

Navigation

Place tabs or pills in the card header.

100%
Loading...
<div class="d-grid gap-3">
    {% for style in ['tabs', 'pills'] %}
        <twig:Card class="text-center">
            <twig:Card:Header>
                <ul class="nav nav-{{ style }} card-header-{{ style }}">
                    <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Active</a></li>
                    <li class="nav-item"><a class="nav-link" href="#">Link</a></li>
                    <li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Disabled</a></li>
                </ul>
            </twig:Card:Header>
            <twig:Card:Body>
                <twig:Card:Title>Special title treatment</twig:Card:Title>
                <twig:Card:Text>Card headers can contain Bootstrap tab or pill navigation.</twig:Card:Text>
            </twig:Card:Body>
        </twig:Card>
    {% endfor %}
</div>

Image caps

Position an image at the top or bottom edge of a card.

100%
Loading...
{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=960&q=80' %}

<div class="row row-cols-1 row-cols-sm-2 g-3">
    <div class="col">
        <twig:Card class="h-100">
            <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
            <twig:Card:Body>
                <twig:Card:Title>Top image cap</twig:Card:Title>
                <twig:Card:Text>Images can sit at either end of a card.</twig:Card:Text>
            </twig:Card:Body>
        </twig:Card>
    </div>
    <div class="col">
        <twig:Card class="h-100">
            <twig:Card:Body>
                <twig:Card:Title>Bottom image cap</twig:Card:Title>
                <twig:Card:Text>The bottom position matches the lower card corners.</twig:Card:Text>
            </twig:Card:Body>
            <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" position="bottom" />
        </twig:Card>
    </div>
</div>

Image overlays

Use an image as the card background and place concise, high-contrast content over it.

100%
Loading...
{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=960&q=80' %}

<twig:Card class="text-bg-dark">
    <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" position="background" />
    <twig:Card:Overlay>
        <twig:Card:Title>Card title</twig:Card:Title>
        <twig:Card:Text>Overlay text should remain shorter than the image height and maintain sufficient contrast.</twig:Card:Text>
        <twig:Card:Text><small>Last updated 3 mins ago</small></twig:Card:Text>
    </twig:Card:Overlay>
</twig:Card>

Horizontal

Combine the component with Bootstrap's responsive grid to create a horizontal card.

100%
Loading...
{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}

<twig:Card class="mb-3" style="max-width: 540px;">
    <div class="row g-0">
        <div class="col-sm-4">
            <img src="{{ image_url }}" class="img-fluid rounded-start h-100 object-fit-cover" alt="A quiet mountain landscape">
        </div>
        <div class="col-sm-8">
            <twig:Card:Body>
                <twig:Card:Title>Card title</twig:Card:Title>
                <twig:Card:Text>This card becomes horizontal at the small breakpoint.</twig:Card:Text>
                <twig:Card:Text><small class="text-body-secondary">Last updated 3 mins ago</small></twig:Card:Text>
            </twig:Card:Body>
        </div>
    </div>
</twig:Card>

Background and color

Use Bootstrap's text-bg-* helpers for contextual card styles.

100%
Loading...
<div class="d-flex flex-wrap gap-3">
    {% for color in ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark'] %}
        <twig:Card class="text-bg-{{ color }}" style="max-width: 18rem;">
            <twig:Card:Header>Header</twig:Card:Header>
            <twig:Card:Body>
                <twig:Card:Title>{{ color|title }} card title</twig:Card:Title>
                <twig:Card:Text>Some quick example text to build on the card title.</twig:Card:Text>
            </twig:Card:Body>
        </twig:Card>
    {% endfor %}
</div>

Borders

Change border and text colors independently with utility classes.

100%
Loading...
<div class="d-flex flex-wrap gap-3">
    {% for color in ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark'] %}
        <twig:Card class="border-{{ color }}" style="max-width: 18rem;">
            <twig:Card:Header class="border-{{ color }}">Header</twig:Card:Header>
            <twig:Card:Body class="text-{{ color }}">
                <twig:Card:Title>{{ color|title }} card title</twig:Card:Title>
                <twig:Card:Text>Use border utilities independently from backgrounds.</twig:Card:Text>
            </twig:Card:Body>
        </twig:Card>
    {% endfor %}
</div>

Mixins utilities

Customize header and footer borders or remove their background color.

100%
Loading...
<twig:Card class="border-success" style="max-width: 18rem;">
    <twig:Card:Header class="bg-transparent border-success">Header</twig:Card:Header>
    <twig:Card:Body class="text-success">
        <twig:Card:Title>Success card title</twig:Card:Title>
        <twig:Card:Text>Header and footer borders can be customized independently.</twig:Card:Text>
    </twig:Card:Body>
    <twig:Card:Footer class="bg-transparent border-success">Footer</twig:Card:Footer>
</twig:Card>

Card groups

Render a set of attached cards with equal width and height.

100%
Loading...
{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}

<div class="card-group">
    {% for length in ['This content is a little bit longer.', 'This card has supporting text.', 'This card has even longer content to demonstrate equal heights.'] %}
        <twig:Card>
            <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
            <twig:Card:Body>
                <twig:Card:Title>Card title</twig:Card:Title>
                <twig:Card:Text>{{ length }}</twig:Card:Text>
            </twig:Card:Body>
            <twig:Card:Footer><small class="text-body-secondary">Last updated 3 mins ago</small></twig:Card:Footer>
        </twig:Card>
    {% endfor %}
</div>

Grid cards

Use responsive grid columns and h-100 cards for a flexible multi-card layout.

100%
Loading...
{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}

<div class="row row-cols-1 row-cols-sm-3 g-4">
    {% for length in ['A longer card with supporting text.', 'A short card.', 'A card with supporting text and an aligned footer.'] %}
        <div class="col">
            <twig:Card class="h-100">
                <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
                <twig:Card:Body>
                    <twig:Card:Title>Card title</twig:Card:Title>
                    <twig:Card:Text>{{ length }}</twig:Card:Text>
                </twig:Card:Body>
                <twig:Card:Footer><small class="text-body-secondary">Last updated 3 mins ago</small></twig:Card:Footer>
            </twig:Card>
        </div>
    {% endfor %}
</div>

API Reference

<twig:Card>

Block Description
content The card content, typically including Card:Header, Card:Body, and Card:Footer.

<twig:Card:Body>

Block Description
content The main content area of the card.

<twig:Card:Footer>

Block Description
content The footer content of the card.

<twig:Card:Header>

Prop Type Default
tag 
'div'|'h2'|'h3'|'h4'|'h5'|'h6' 'div'
Block Description
content The header content of the card.

<twig:Card:Image>

Prop Type Default
src 
string -
alt 
string ''
position 
'top'|'bottom'|'background' 'top'
Prop Type Default
href 
string '#'
Block Description
content The link label.

<twig:Card:Overlay>

Block Description
content The content displayed over the card image.

<twig:Card:Subtitle>

Prop Type Default
tag 
'h2'|'h3'|'h4'|'h5'|'h6'|'div' 'h6'
Block Description
content The subtitle text of the card.

<twig:Card:Text>

Prop Type Default
tag 
'p'|'div' 'p'
Block Description
content The supporting text of the card.

<twig:Card:Title>

Prop Type Default
tag 
'h2'|'h3'|'h4'|'h5'|'h6'|'div' 'h5'
Block Description
content The title text of the card.