Skip to main content

Testing

Help us to improve the ONS Design System. Take part in a short study

Card

The card component provides a navigation mechansim consisting of a title and link, descriptive text and an optional list of further links.

<div class="card" aria-labelledBy="title" aria-describedBy="text">
  <h3 class="u-fs-m" id="title">
    <a href="#0">Your data and security</a>
  </h3>
  <p id="text">How we keep your data safe and what happens to your personal information.</p>
</div>
Nunjucks macro options
Name Type Required Description
url string false Will wrap the text in a link
title string true The title for the card element
titleSize string false Number used to determine heading level
text string true The text for the card element
textId string true Id of text for the card element aria-describedBy attribute
titleClasses string false Font size class for the card title
image Image false An object containing path attributes for the image

Image

Name Type Required Description
smallSrc string true Path to the non-retina version of the image
largeSrc string true Path to the retina version of the image
alt string true Alt tag to explain the appearance and function of the image
{% from "components/card/_macro.njk" import onsCard %}
{{ onsCard({
    "id": 'title',
    "textId": 'text',
    "titleSize": '3',
    "title": 'Your data and security',
    "url": '#0',
    "text": 'How we keep your data safe and what happens to your personal information.'
}) }}

{%- macro onsCard(params) -%}
  {% from "components/lists/_macro.njk" import onsList %}
  {% set titleSize = params.titleSize | default('2') %}
  <div class="card" aria-labelledBy="{{ params.id }}" aria-describedBy="{{ params.textId }}">
    {%- if params.image -%}
      <a href="{{ params.url }}" class="card__link u-db">
        {% if params.image is defined and params.image and params.image.smallSrc is defined and params.image.smallSrc %}
            <img class="card__image u-mb-s" height="200" width="303" srcset="{{ params.image.smallSrc }} 1x, {{ params.image.largeSrc }} 2x" src="{{ params.image.smallSrc }}" alt="{{ params.image.alt }}" loading="lazy">
        {% else %}
            <img class="card__image u-mb-s" height="100" width="303" srcset="{{ params.placeholderURL if params.placeholderURL is defined and params.placeholderURL }}/img/small/placeholder-card.png 1x, {{ params.placeholderURL if params.placeholderURL is defined and params.placeholderURL }}/img/large/placeholder-card.png 2x" src="{{ params.placeholderURL if params.placeholderURL is defined and params.placeholderURL }}/img/small/placeholder-card.png" alt="Image placeholder" loading="lazy">
        {% endif %}
        <h{{ titleSize }} class="card__title {{ params.titleClasses | default('u-fs-m')}}" id="{{ params.id }}">
          {{ params.title }}
        </h{{ titleSize }}>
      </a>
    {%- else -%}
      <h{{ titleSize }} class="{{ params.titleClasses | default('u-fs-m') }}" id="{{ params.id }}">
        <a href="{{ params.url }}">{{ params.title }}</a>
      </h{{ titleSize }}>
    {%- endif -%}
    <p id="{{ params.textId }}">{{ params.text }}</p>
    {% if params.itemsList is defined and params.itemsList -%}
      {{
          onsList({
              "classes": 'list--dashed',
              "itemsList": params.itemsList
          })
      }}
    {% endif %}
  </div>
{%- endmacro -%}

.card {
  margin: 0 0 2rem;
  width: 100%;
  &__link:hover {
    text-decoration-thickness: 3px;
  }
  @include mq(m) {
    margin: 0;
    .grid__col & {
      padding-right: 1rem;
    }
  }
}

When to use this component

Use the card component to signpost users to pages and to provide context and further information on the contents of the destination page.

Good examples of where the use of cards work well include; the homepage, to create a collection of the main areas of the service and provide further affordance of what the service offers; hub level pages, to show the key content areas of what could be a deep structure; as a consistent pattern of navigation at any level within a site structure.

How to use this component

Cards should be used in the main container of the page and not within side bars, footers etc. The card component needs to be implemented using the grid component and the various layout options available to display cards in rows. A row should contain no more than 3 or 4 cards and stack vertically on small viewports. The html tab shows the various classes used to achieve the layout.

Card set

The example below shows a row of 3 cards.

<div class="container">
  <div class="grid grid--column@xxs@s">
    <div class="grid__col col-4@m">
      <div class="card" aria-labelledBy="title1" aria-describedBy="text1">
        <h2 class="u-fs-m" id="title1">
          <a href="#0">About the census</a>
        </h2>
        <p id="text1">The census is a survey that gives us information about all the households in England and Wales.</p>
      </div>
    </div>
    <div class="grid__col col-4@m">
      <div class="card" aria-labelledBy="title2" aria-describedBy="text2">
        <h2 class="u-fs-m" id="title2">
          <a href="#0">Working on the census</a>
        </h2>
        <p id="text2">For Census 2021, we’ll be hiring at least 30,000 field staff across England and Wales.</p>
      </div>
    </div>
    <div class="grid__col col-4@m">
      <div class="card" aria-labelledBy="title3" aria-describedBy="text3">
        <h2 class="u-fs-m" id="title3">
          <a href="#0">Your data and security</a>
        </h2>
        <p id="text3">How we keep your data safe and what happens to your personal information.</p>
      </div>
    </div>
  </div>
</div>
Nunjucks macro options
Name Type Required Description
url string false Will wrap the text in a link
title string true The title for the card element
titleSize string false Number used to determine heading level
text string true The text for the card element
textId string true Id of text for the card element aria-describedBy attribute
titleClasses string false Font size class for the card title
image Image false An object containing path attributes for the image

Image

Name Type Required Description
smallSrc string true Path to the non-retina version of the image
largeSrc string true Path to the retina version of the image
alt string true Alt tag to explain the appearance and function of the image
{% from "components/card/_macro.njk" import onsCard %}
<div class="container">
    <div class="grid grid--column@xxs@s">
        <div class="grid__col col-4@m">
            {{ onsCard({
                "id": 'title1',
                "textId": 'text1',
                "title": 'About the census',
                "url": '#0',
                "text": 'The census is a survey that gives us information about all the households in England and Wales.'
            }) }}
        </div>
        <div class="grid__col col-4@m">
            {{ onsCard({
                "id": 'title2',
                "textId": 'text2',
                "title": 'Working on the census',
                "url": '#0',
                "text": 'For Census 2021, we’ll be hiring at least 30,000 field staff across England and Wales.'
            }) }}
        </div>
        <div class="grid__col col-4@m">
            {{ onsCard({
                "id": 'title3',
                "textId": 'text3',
                "title": 'Your data and security',
                "url": '#0',
                "text": 'How we keep your data safe and what happens to your personal information.'
            }) }}
        </div>
    </div>
</div>

{%- macro onsCard(params) -%}
  {% from "components/lists/_macro.njk" import onsList %}
  {% set titleSize = params.titleSize | default('2') %}
  <div class="card" aria-labelledBy="{{ params.id }}" aria-describedBy="{{ params.textId }}">
    {%- if params.image -%}
      <a href="{{ params.url }}" class="card__link u-db">
        {% if params.image is defined and params.image and params.image.smallSrc is defined and params.image.smallSrc %}
            <img class="card__image u-mb-s" height="200" width="303" srcset="{{ params.image.smallSrc }} 1x, {{ params.image.largeSrc }} 2x" src="{{ params.image.smallSrc }}" alt="{{ params.image.alt }}" loading="lazy">
        {% else %}
            <img class="card__image u-mb-s" height="100" width="303" srcset="{{ params.placeholderURL if params.placeholderURL is defined and params.placeholderURL }}/img/small/placeholder-card.png 1x, {{ params.placeholderURL if params.placeholderURL is defined and params.placeholderURL }}/img/large/placeholder-card.png 2x" src="{{ params.placeholderURL if params.placeholderURL is defined and params.placeholderURL }}/img/small/placeholder-card.png" alt="Image placeholder" loading="lazy">
        {% endif %}
        <h{{ titleSize }} class="card__title {{ params.titleClasses | default('u-fs-m')}}" id="{{ params.id }}">
          {{ params.title }}
        </h{{ titleSize }}>
      </a>
    {%- else -%}
      <h{{ titleSize }} class="{{ params.titleClasses | default('u-fs-m') }}" id="{{ params.id }}">
        <a href="{{ params.url }}">{{ params.title }}</a>
      </h{{ titleSize }}>
    {%- endif -%}
    <p id="{{ params.textId }}">{{ params.text }}</p>
    {% if params.itemsList is defined and params.itemsList -%}
      {{
          onsList({
              "classes": 'list--dashed',
              "itemsList": params.itemsList
          })
      }}
    {% endif %}
  </div>
{%- endmacro -%}

.card {
  margin: 0 0 2rem;
  width: 100%;
  &__link:hover {
    text-decoration-thickness: 3px;
  }
  @include mq(m) {
    margin: 0;
    .grid__col & {
      padding-right: 1rem;
    }
  }
}

Card set with list

The example below shows a row of 3 cards with a list of links that relate to the main heading link.

<div class="container">
  <div class="grid grid--column@xxs@s">
    <div class="grid__col col-4@m">
      <div class="card" aria-labelledBy="title1" aria-describedBy="text1">
        <h2 class="u-fs-m" id="title1">
          <a href="#0">About the census</a>
        </h2>
        <p id="text1">The census is a survey that gives us information about all the households in England and Wales.</p>
        <ul class="list list--dashed">
          <li class="list__item ">
            <a href="#0" class="list__link ">List item 1</a>
          </li>
          <li class="list__item ">
            <a href="#0" class="list__link ">List item 2</a>
          </li>
        </ul>
      </div>
    </div>
    <div class="grid__col col-4@m">
      <div class="card" aria-labelledBy="title2" aria-describedBy="text2">
        <h2 class="u-fs-m" id="title2">
          <a href="#0">Working on the census</a>
        </h2>
        <p id="text2">For Census 2021, we’ll be hiring at least 30,000 field staff across England and Wales.</p>
        <ul class="list list--dashed">
          <li class="list__item ">
            <a href="#0" class="list__link ">List item 1</a>
          </li>
          <li class="list__item ">
            <a href="#0" class="list__link ">List item 2</a>
          </li>
        </ul>
      </div>
    </div>
    <div class="grid__col col-4@m">
      <div class="card" aria-labelledBy="title3" aria-describedBy="text3">
        <h2 class="u-fs-m" id="title3">
          <a href="#0">Your data and security</a>
        </h2>
        <p id="text3">How we keep your data safe and what happens to your personal information.</p>
        <ul class="list list--dashed">
          <li class="list__item ">
            <a href="#0" class="list__link ">List item 1</a>
          </li>
          <li class="list__item ">
            <a href="#0" class="list__link ">List item 2</a>
          </li>
        </ul>
      </div>
    </div>
  </div>
</div>
Nunjucks macro options
Name Type Required Description
url string false Will wrap the text in a link
title string true The title for the card element
titleSize string false Number used to determine heading level
text string true The text for the card element
textId string true Id of text for the card element aria-describedBy attribute
titleClasses string false Font size class for the card title
image Image false An object containing path attributes for the image

Image

Name Type Required Description
smallSrc string true Path to the non-retina version of the image
largeSrc string true Path to the retina version of the image
alt string true Alt tag to explain the appearance and function of the image
{% from "components/card/_macro.njk" import onsCard %}
<div class="container">
    <div class="grid grid--column@xxs@s">
        <div class="grid__col col-4@m">
            {{ onsCard({
                "id": 'title1',
                "textId": 'text1',
                "title": 'About the census',
                "url": '#0',
                "text": 'The census is a survey that gives us information about all the households in England and Wales.',
                "itemsList": [
                    {
                        "url": '#0',
                        "text": 'List item 1'
                    },
                    {
                        "url": '#0',
                        "text": 'List item 2'
                    }
                ]
            }) }}
        </div>
        <div class="grid__col col-4@m">
            {{ onsCard({
                "id": 'title2',
                "textId": 'text2',
                "title": 'Working on the census',
                "url": '#0',
                "text": 'For Census 2021, we’ll be hiring at least 30,000 field staff across England and Wales.',
                "itemsList": [
                    {
                        "url": '#0',
                        "text": 'List item 1'
                    },
                    {
                        "url": '#0',
                        "text": 'List item 2'
                    }
                ]
            }) }}
        </div>
        <div class="grid__col col-4@m">
            {{ onsCard({
                "id": 'title3',
                "textId": 'text3',
                "title": 'Your data and security',
                "url": '#0',
                "text": 'How we keep your data safe and what happens to your personal information.',
                "itemsList": [
                    {
                        "url": '#0',
                        "text": 'List item 1'
                    },
                    {
                        "url": '#0',
                        "text": 'List item 2'
                    }
                ]
            }) }}
        </div>
    </div>
</div>

{%- macro onsCard(params) -%}
  {% from "components/lists/_macro.njk" import onsList %}
  {% set titleSize = params.titleSize | default('2') %}
  <div class="card" aria-labelledBy="{{ params.id }}" aria-describedBy="{{ params.textId }}">
    {%- if params.image -%}
      <a href="{{ params.url }}" class="card__link u-db">
        {% if params.image is defined and params.image and params.image.smallSrc is defined and params.image.smallSrc %}
            <img class="card__image u-mb-s" height="200" width="303" srcset="{{ params.image.smallSrc }} 1x, {{ params.image.largeSrc }} 2x" src="{{ params.image.smallSrc }}" alt="{{ params.image.alt }}" loading="lazy">
        {% else %}
            <img class="card__image u-mb-s" height="100" width="303" srcset="{{ params.placeholderURL if params.placeholderURL is defined and params.placeholderURL }}/img/small/placeholder-card.png 1x, {{ params.placeholderURL if params.placeholderURL is defined and params.placeholderURL }}/img/large/placeholder-card.png 2x" src="{{ params.placeholderURL if params.placeholderURL is defined and params.placeholderURL }}/img/small/placeholder-card.png" alt="Image placeholder" loading="lazy">
        {% endif %}
        <h{{ titleSize }} class="card__title {{ params.titleClasses | default('u-fs-m')}}" id="{{ params.id }}">
          {{ params.title }}
        </h{{ titleSize }}>
      </a>
    {%- else -%}
      <h{{ titleSize }} class="{{ params.titleClasses | default('u-fs-m') }}" id="{{ params.id }}">
        <a href="{{ params.url }}">{{ params.title }}</a>
      </h{{ titleSize }}>
    {%- endif -%}
    <p id="{{ params.textId }}">{{ params.text }}</p>
    {% if params.itemsList is defined and params.itemsList -%}
      {{
          onsList({
              "classes": 'list--dashed',
              "itemsList": params.itemsList
          })
      }}
    {% endif %}
  </div>
{%- endmacro -%}

.card {
  margin: 0 0 2rem;
  width: 100%;
  &__link:hover {
    text-decoration-thickness: 3px;
  }
  @include mq(m) {
    margin: 0;
    .grid__col & {
      padding-right: 1rem;
    }
  }
}

Cards with images

The example below shows a row of 3 cards with images.

<div class="container">
  <div class="grid grid--column@xxs@s">
    <div class="grid__col col-4@m">
      <div class="card" aria-labelledBy="title1" aria-describedBy="text1"><a href="#0" class="card__link u-db">
          <img class="card__image u-mb-s" height="200" width="303" srcset="/img/small/placeholder-card.png 1x, /img/large/placeholder-card.png 2x" src="/img/small/placeholder-card.png" alt="Image placeholder" loading="lazy">
          <h2 class="card__title u-fs-m" id="title1">
            About the census
          </h2>
        </a>
        <p id="text1">The census is a survey that gives us information about all the households in England and Wales.</p>
      </div>
    </div>
    <div class="grid__col col-4@m">
      <div class="card" aria-labelledBy="title2" aria-describedBy="text2"><a href="#0" class="card__link u-db">
          <img class="card__image u-mb-s" height="200" width="303" srcset="/img/small/placeholder-card.png 1x, /img/large/placeholder-card.png 2x" src="/img/small/placeholder-card.png" alt="Image placeholder" loading="lazy">
          <h2 class="card__title u-fs-m" id="title2">
            Working on the census
          </h2>
        </a>
        <p id="text2">For Census 2021, we’ll be hiring at least 30,000 field staff across England and Wales.</p>
      </div>
    </div>
    <div class="grid__col col-4@m">
      <div class="card" aria-labelledBy="title3" aria-describedBy="text3"><a href="#0" class="card__link u-db">
          <img class="card__image u-mb-s" height="200" width="303" srcset="/img/small/placeholder-card.png 1x, /img/large/placeholder-card.png 2x" src="/img/small/placeholder-card.png" alt="Image placeholder" loading="lazy">
          <h2 class="card__title u-fs-m" id="title3">
            Your data and security
          </h2>
        </a>
        <p id="text3">How we keep your data safe and what happens to your personal information.</p>
      </div>
    </div>
  </div>
</div>
Nunjucks macro options
Name Type Required Description
url string false Will wrap the text in a link
title string true The title for the card element
titleSize string false Number used to determine heading level
text string true The text for the card element
textId string true Id of text for the card element aria-describedBy attribute
titleClasses string false Font size class for the card title
image Image false An object containing path attributes for the image

Image

Name Type Required Description
smallSrc string true Path to the non-retina version of the image
largeSrc string true Path to the retina version of the image
alt string true Alt tag to explain the appearance and function of the image
{% from "components/card/_macro.njk" import onsCard %}
<div class="container">
    <div class="grid grid--column@xxs@s">
        <div class="grid__col col-4@m">
            {{ onsCard({
                "id": 'title1',
                "textId": 'text1',
                "title": 'About the census',
                "url": '#0',
                "text": 'The census is a survey that gives us information about all the households in England and Wales.',
                "image": {
                    "smallSrc": '/img/small/placeholder-card.png',
                    "largeSrc": '/img/large/placeholder-card.png',
                    "alt": 'Image placeholder'
                }
            }) }}
        </div>
        <div class="grid__col col-4@m">
            {{ onsCard({
                "id": 'title2',
                "textId": 'text2',
                "title": 'Working on the census',
                "url": '#0',
                "text": 'For Census 2021, we’ll be hiring at least 30,000 field staff across England and Wales.',
                "image": {
                    "smallSrc": '/img/small/placeholder-card.png',
                    "largeSrc": '/img/large/placeholder-card.png',
                    "alt": 'Image placeholder'
                }
            }) }}
        </div>
        <div class="grid__col col-4@m">
            {{ onsCard({
                "id": 'title3',
                "textId": 'text3',
                "title": 'Your data and security',
                "url": '#0',
                "text": 'How we keep your data safe and what happens to your personal information.',
                "image": {
                    "smallSrc": '/img/small/placeholder-card.png',
                    "largeSrc": '/img/large/placeholder-card.png',
                    "alt": 'Image placeholder'
                }
            }) }}
        </div>
    </div>
</div>

{%- macro onsCard(params) -%}
  {% from "components/lists/_macro.njk" import onsList %}
  {% set titleSize = params.titleSize | default('2') %}
  <div class="card" aria-labelledBy="{{ params.id }}" aria-describedBy="{{ params.textId }}">
    {%- if params.image -%}
      <a href="{{ params.url }}" class="card__link u-db">
        {% if params.image is defined and params.image and params.image.smallSrc is defined and params.image.smallSrc %}
            <img class="card__image u-mb-s" height="200" width="303" srcset="{{ params.image.smallSrc }} 1x, {{ params.image.largeSrc }} 2x" src="{{ params.image.smallSrc }}" alt="{{ params.image.alt }}" loading="lazy">
        {% else %}
            <img class="card__image u-mb-s" height="100" width="303" srcset="{{ params.placeholderURL if params.placeholderURL is defined and params.placeholderURL }}/img/small/placeholder-card.png 1x, {{ params.placeholderURL if params.placeholderURL is defined and params.placeholderURL }}/img/large/placeholder-card.png 2x" src="{{ params.placeholderURL if params.placeholderURL is defined and params.placeholderURL }}/img/small/placeholder-card.png" alt="Image placeholder" loading="lazy">
        {% endif %}
        <h{{ titleSize }} class="card__title {{ params.titleClasses | default('u-fs-m')}}" id="{{ params.id }}">
          {{ params.title }}
        </h{{ titleSize }}>
      </a>
    {%- else -%}
      <h{{ titleSize }} class="{{ params.titleClasses | default('u-fs-m') }}" id="{{ params.id }}">
        <a href="{{ params.url }}">{{ params.title }}</a>
      </h{{ titleSize }}>
    {%- endif -%}
    <p id="{{ params.textId }}">{{ params.text }}</p>
    {% if params.itemsList is defined and params.itemsList -%}
      {{
          onsList({
              "classes": 'list--dashed',
              "itemsList": params.itemsList
          })
      }}
    {% endif %}
  </div>
{%- endmacro -%}

.card {
  margin: 0 0 2rem;
  width: 100%;
  &__link:hover {
    text-decoration-thickness: 3px;
  }
  @include mq(m) {
    margin: 0;
    .grid__col & {
      padding-right: 1rem;
    }
  }
}

How to use this component

If using the card pattern with images, we have two versions of the image, one for standard screens and the other for retina screens.

Image dimensions (width × height):

  • standard screen is: 303px × 200px
  • retina screen is: 606px × 400px

We specify a small and a large image path, which is defined in the macro as smallSrc and largeSrc.

Help improve this component

Let us know how we could improve this component or share your user research findings.

Discuss the ‘Card’ component on GitHub