Jump to main content

Tab section


Work in progress

Vanilla's patterns are newly released and still evolving as we receive feedback.

A tab section is used to organize related content into separate tabs within a section. It combines a title, optional description, and optional call-to-action with a tabbed interface that can contain various types of content blocks.

The tab section pattern is composed of the following elements:

Element Description
Title (required) Main heading text (h2)
Description Optional description text
Call-to-Action Optional CTA buttons/links
Tabs (required) Tabs that hold a variety of content blocks

Layout Options

The tab section pattern supports responsive grid layouts that adapt to different screen sizes.

Note: the blocks supported vary by layout. See content blocks for details.

Full-width

The full-width layout spans the entire content area with the tabs taking up all available space.

50/50 Layout

The 50/50 layout creates a two-column grid that splits evenly on large screens. The first column contains the title (and optional description/CTA), while the second column (50% width) contains the tabs.

25/75 Layout

The 25/75 layout creates an asymmetrical two-column layout where the first column (25%) contains the title (and optional description/CTA), and the second column (75%) contains the tabs.

Top Rule Variants

By default, the tab section has a default horizontal rule at the top. You can customize this or disable it entirely using the top_rule_variant parameter.

Supported variants:

  • "default" - Standard horizontal rule
  • "muted" - Lighter/muted horizontal rule
  • "none" - No rule displayed

Padding Variants

Tab sections support customizable padding options using the section pattern. By default, the pattern is wrapped in a regular section with default padding.

For different spacing needs, you may instead use:

  • "default" - p-section for standard spacing (default)
  • "deep" - p-section--deep for maximum spacing
  • "shallow" - p-section--shallow for reduced spacing

Please refer to the section documentation for more guidance on padding selection.

Title

The title is the main heading of the section. By default, it is an <h2> heading. You may customize the heading level and/or wrap the title in a link.

Avoid duplicate section titles.

The tab JavaScript relies on section title uniqueness to open the correct content pane.
Duplicate titles will result in tabs controlling incorrect panes.

Linked title

The title can be made into a link by providing link attributes.

{
  "title": {
    "text": "Your title here",
    "link_attrs": {
      "href": "#"
    }
  }
}
  • text: The title text to display
  • link_attrs: HTML attributes for the anchor element (href, class, etc.)
  • heading_level: Optional. Heading level (2, 3, or 4). Default: 2

Custom heading level

You can customize the heading level of the title.

{
  "title": {
    "text": "Your title here",
    "heading_level": 3
  }
}

Description

Add a description to provide context or additional information.

{
  "description": {
    "content": "Your description text here",
    "type": "text"
  }
}
  • content: The description text or HTML content
  • type: Optional. Either "text" (default) or "html". Text content is wrapped in <p> tags, HTML content is rendered as-is.

Call-to-action

Add CTA buttons and/or links to encourage user action.

{
  "cta": {
    "primary": {
      "content_html": "Primary button text",
      "attrs": {
        "href": "link-url",
        "class": "optional-css-class"
      }
    },
    "secondaries": [
      {
        "content_html": "Secondary button text",
        "attrs": {
          "href": "link-url"
        }
      }
    ],
    "link": {
      "content_html": "Link text",
      "attrs": {
        "href": "link-url"
      }
    }
  }
}
  • primary: Optional primary button configuration
  • secondaries: Optional array of secondary button configurations (typically 0-2)
  • link: Optional text link configuration

Each CTA configuration accepts:

  • content_html: The inner HTML of the CTA item
  • attrs: HTML attributes for the button/link. If href is present, the element will be an <a> tag, otherwise a <button> tag.

Content blocks

Tab sections use a flexible block model where each tab contains a single content block. The type of block determines what configuration is required.

Note: Different layouts support different content block types. When you provide a tab with an unsupported block type for the selected layout, it will be silently skipped. Always ensure your content blocks match your chosen layout:

Block Type Full-width 50/50 25/75
Quote
Linked Logo
Logo Block
Divided Section
Blog
Basic Section

Block type can be chosen with the tabs[].type property. tabs.item contains the block's configuration, which varies from block to block and is demonstrated in the JSON snippets below.

Quote block

Quote blocks display testimonials or highlighted quotes with optional attribution and images.

{
  "signpost": {
    "image": {
      "html": "<img src=\"image-url\" alt=\"Logo\">"
    }
  },
  "contents": {
    "citation": {
      "name": "Person name",
      "organisation": "Organization",
      "title": "Job title"
    },
    "body": {
      "size": "large",
      "text": "The quote text here"
    },
    "cta": {
      "html": "<a href=\"#\">Learn more &rsaquo;</a>"
    },
    "image": {
      "html": "<img src=\"image-url\" alt=\"\">"
    }
  }
}
  • signpost (Object, Optional) – Signpost configuration with image
  • contents (Object, Required) – Quote contents with:
    • citation (Object, Optional) - Attribution details:
      • name (String, Optional) – Person name
      • organisation (String, Optional) – Organization name
      • title (String, Optional) – Job title
    • body (Object, Optional) – Quote body with:
      • size: "small", "medium", or "large"
      • text: The quote text
    • cta (Object, Optional): CTA configuration with:
      • html (String, required): HTML content for the CTA link
    • image (Object, Optional): Image configuration with:
      • html (String, required): HTML content for the image

Linked Logo Block

The linked logo block displays a set of clickable logos.

{
  "links": [
    {
      "href": "link-url",
      "text": "Link text",
      "label": "Aria label",
      "image_attrs": {
        "src": "logo-url",
        "alt": "alt-text",
        "width": "200",
        "height": "100"
      }
    }
  ]
}
  • links (Array, Required) - Array of link objects. Each link has:
    • href: URL the logo links to
    • text: Text for the link
    • label: Aria label for accessibility
    • image_attrs: Image attributes (src, alt, width, height, etc.)

Logo Block

The logo block displays a cloud of logos.

{
  "is_fixed_width": true,
  "logos": [
    {
      "attrs": {
        "src": "logo-url",
        "alt": "alt-text"
      },
      "has_line_break_after": false
    }
  ]
}
  • is_fixed_width (Boolean, Optional) - Whether to wrap logos in a fixed-width container. Default: true
  • logos (Array, Required) - Array of logo objects with:
    • attrs: Image attributes (src, alt, class, etc.)
    • has_line_break_after: Whether to force a line break after this logo

Divided Section Block

The divided section block displays divided section content blocks.

{
  "blocks": [
    {
      "type": "description",
      "item": {
        "type": "text",
        "content": "Content here"
      }
    }
  ]
}

Blog Block

The blog block displays a grid of blog articles.

Blog articles may be defined statically (as shown below), or pulled dynamically. See the blog docs for more information.

{
  "articles": [
    {
      "title": {
        "text": "How to enable Real-time Ubuntu on your machine",
        "link_attrs": {
          "href": "#"
        }
      },
      "description": {
        "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
      },
      "metadata": {
        "authors": [
          {
            "text": "John Doe",
            "link_attrs": {
              "href": "#"
            }
          }
        ],
        "date": {
          "text": "15 March 2025"
        }
      }
    },
    {
      "title": {
        "text": "How to enable Real-time Ubuntu on your machine",
        "link_attrs": {
          "href": "#"
        }
      },
      "description": {
        "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
      },
      "metadata": {
        "authors": [
          {
            "text": "John Doe",
            "link_attrs": {
              "href": "#"
            }
          }
        ],
        "date": {
          "text": "15 March 2025"
        }
      }
    }
  ]
}
  • articles (Array, Required) - Array of article objects
  • template_config (Object, Optional) - Additional template configuration for dynamically pulling blog articles. See dynamic blog for details.

Basic Section Block

The basic section block allows you to embed basic section content blocks.

{
  "items": [
    {
      "type": "description",
      "item": {
        "type": "text",
        "content": "Content here"
      }
    },
    {# .. other items, following the basic section content block API #} 
  ]
}

Jinja Macro

The vf_tab_section Jinja macro can be used to generate a tab section pattern. The API for the macro is shown below.

Parameters

Name Required? Type Default Description
title Yes Object N/A Title configuration object with text and optional link_attrs and heading_level
title.text Yes string N/A The main title text (rendered as h2 by default)
title.link_attrs No Object N/A Attributes of an anchor element, as a dictionary. See attribute forwarding docs for more info.
title.heading_level No number 2 Heading level for the title (2, 3, or 4)
description No Object {} Description configuration object with content and optional type.
description.content No string "" Description text or HTML content
description.type No "text" | "html" "text" Content type. "text" wraps content in <p>, "html" renders as-is.
cta No Object {} Call-to-action configuration with primary, secondaries, and/or link. Only displayed in 50/50 and 25/75 layouts.
layout No One of:
'full-width',
'50-50',
'25-75'
'50-50' Layout variant for the section. Different layouts allow different content block types.
padding No One of:
'deep',
'shallow',
'default'
'default' Padding variant for the entire section. See section pattern for details.
top_rule_variant No One of:
'default',
'muted',
'none'
'default' Variant of horizontal rule to display at the top of the section.
tabs Yes Array<Object> N/A Array of tab configurations. See Content Blocks for structure details. Unsupported blocks for the selected layout will be silently skipped.
tabs[].type Yes One of:
'quote',
'linked-logo',
'logo-block',
'divided-section',
'blog',
'basic-section'
N/A The content block type for this tab. Availability depends on the layout parameter.
tabs[].item Yes Object N/A Configuration specific to the block type. See Content Blocks for details on each type.
tabs[].tab_html Yes string "" HTML content for the tab label.
attrs No Object N/A Attributes to apply to the section element, as a dictionary. See attribute forwarding docs for more info.

Import

Jinja Macro

To import the Tab Section Jinja macro, copy the following import statement into your Jinja template.

{% from "_macros/vf_tab-section.jinja" import vf_tab_section %}

View the building with Jinja macros guide for macro installation instructions.

JavaScript

The tabs JS module must be added to the page in order for tabs to function.

Please follow the Vanilla JS module installation instructions. The specific import depends on your build process:

Webpack

If using Webpack to bundle Vanilla JS, import and initialize the tabs component on your page:

// Import the 'tabs' module
import {tabs} from 'vanilla-framework/js';

// Initialize the tabs component
tabs.initTabs('[role="tablist"]');

No bundler

If not using Webpack or a similar bundler, copy the module to your static directory in a build stage, and add the tabs module directly to your page:

<script type="module" src="/static/js/modules/vanilla-framework/js/tabs.js"></script>

SCSS

Since Patterns leverage many other parts of Vanilla in their composition and content, we recommend importing the entirety of Vanilla for full support.