---
title: "Components"
manual: "Frontend Theme for Extension Development"
version: "main"
source: "Components/Index.rst"
modified: "2026-09-17T04:52:50+00:00"
---

# Components

The stylesheet the theme ships is a component library. Every class carries a
`theme-` prefix, and every component states the markup it expects in the
header comment of its own SCSS file below `Resources/Private/Scss/`. That
comment is the contract a template is written against; this page is the
overview. The `/styleguide` page of the seeded demo tree renders every
component, in the appearance and palette currently selected - see
[Feature: A styleguide page](../Changelog/2.0/Feature-Styleguide.html#feature-styleguide).

Every component reads its values through custom properties with a fallback, so
a site package re-themes one component or all of them from its own CSS, without
rebuilding the SCSS - see [Feature: Component library](../Changelog/2.0/Feature-ComponentLibrary.html#feature-component-library).

Every state the library draws in colour - a selected tab, a pressed toggle -
also survives forced colours, the high contrast themes of the operating system,
where it is drawn in the system highlight colour instead.

## What the library contains

| Group | Components |
| --- | --- |
| Content | Accordion, alert, author, badge, card - in a grid, with a column count or as a row that scrolls sideways -, code block, description list - with a divided variant for key and value - call to action, divider, feature and feature grid, figure, file list, hero, list, list group, media object, panel, pricing, quote, stats, steps, table, teaser, timeline, and the text roles display, lead and eyebrow |
| Data display | Avatar and avatar group, tag list, progress bar, meter |
| Actions | Button - with a link and an icon variant, pressed and busy states, and an attached button group - and the close button |
| Icons | The solid icons of Font Awesome Free, inline, see [Icons](#icons) |
| Interactive | Tabs, dialog, tooltip, toggletip |
| Forms | Controls, field, input group, choice group, validation |
| Navigation and page | Main and sub navigation, breadcrumb, pagination, content menu - which is also the table of contents of a page - language menu, dropdown, skip link, gallery, the content element wrapper, site header and site footer |

## Icons

The theme ships the complete solid style of Font Awesome Free 7.3.1 - 2001
icons - below `Resources/Public/Icons/FontAwesome/Solid/`, and draws every
icon of its own from it. A template renders one with the ViewHelper
`<theme:icon>`, by the file name of the icon without `.svg`:

```html
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:theme="http://typo3.org/ns/SBUERK/ThemeExtensionDevelopment/ViewHelpers"
      data-namespace-typo3-fluid="true">

<theme:icon name="circle-info" />
<theme:icon name="circle-info" label="Information" />
```

The icon is inlined as SVG: no webfont, no CDN and no request is involved. It is
as large as the text around it and takes the colour of that text. Without
`label` it is decoration and hidden from screen readers - the right choice
next to text or inside a button that has a name of its own. With
`label` it is an image with that name. An icon name that does not exist
stops the rendering with an exception rather than leaving a gap.

The optional `class` argument adds classes to `theme-icon`. A
component that gives icons a fixed size sets the custom property
`--theme-icon-size` on the element around them.

The icons are licensed under CC BY 4.0; the npm package they come from declares
`CC-BY-4.0 AND OFL-1.1 AND MIT`, of which only the icons ship here. The
licence of the package and an attribution ship next to them, in
`Resources/Public/Icons/FontAwesome/LICENSE.txt` and
`Resources/Public/Icons/FontAwesome/ATTRIBUTION.txt`. Every icon file
keeps the attribution comment it comes with, and so does every icon on a
rendered page. The extension itself stays GPL-2.0-or-later.

In the backend, an editor picks an icon from a select list grouped by the
categories of Font Awesome, with a grid of the icons under it; page TSconfig
`keepItems` narrows the list per field - see
[Feature: Editors pick an icon by name](../Changelog/2.0/Feature-IconPicker.html#feature-icon-picker).

## Components that need JavaScript

Three components need the theme's script,
`Resources/Public/JavaScript/theme.js`, which the page includes as a
module. Each of them stays usable without it - with JavaScript switched off,
and with JavaScript on but the script failing to load. The dialog opener
follows the `data-js` attribute that the theme's inline head script sets
on the `<html>` element before the page is painted, the same marker the
main navigation's toggle depends on. The tabs follow a marker of their own,
`data-theme-tabs-bound`, which only the theme's script sets on the tab
group once it has bound it.

| Component | With JavaScript | Without JavaScript |
| --- | --- | --- |
| Tabs | The WAI-ARIA tabs pattern: the arrow keys, Home and End move between the tabs, one panel is shown at a time. | No tab list. Every panel is shown, each under its own heading, and none of them claims to be a tab panel. |
| Dialog | A button carrying `data-theme-dialog-open` opens the `<dialog>` of that id as a modal. A click on the backdrop closes it, and focus returns to the button. | The button is not shown, and the dialog stays closed. |
| Tooltip | Escape hides the tooltip without moving focus. | Shown on hover and on focus, but Escape does not hide it. |

### Tabs

The first tab is the selected one, and a panel carries nothing but its class
and its id in the markup. Hiding the panels that are not selected is the
script's job, and so are the tab panel role, the label and the tab stop of each
panel, so a page without the script shows every panel as plain content. Each
panel repeats the label of its tab in a `theme-tabs__heading`, which
labels the panel when there are no tabs to do it.

```html
<div class="theme-tabs">
    <div class="theme-tabs__list" role="tablist" aria-label="Delivery">
        <button class="theme-tabs__tab" type="button" role="tab" id="t-set"
                aria-selected="true" aria-controls="t-set-panel">Site set</button>
        <button class="theme-tabs__tab" type="button" role="tab" id="t-static"
                aria-selected="false" aria-controls="t-static-panel" tabindex="-1">Static include</button>
    </div>
    <div class="theme-tabs__panel" id="t-set-panel">
        <h3 class="theme-tabs__heading">Site set</h3>
        …
    </div>
    <div class="theme-tabs__panel" id="t-static-panel">
        <h3 class="theme-tabs__heading">Static include</h3>
        …
    </div>
</div>
```

### Dialog

A native `<dialog>`: the browser keeps focus inside it, makes the page
behind it inert, and closes it on Escape. The `<form method="dialog">`
around its content closes it with no script at all. Because nothing can open it
without JavaScript, a dialog is for content a page can do without - a
confirmation for an action that needs the script anyway.

```html
<button class="theme-button" type="button" aria-haspopup="dialog"
        data-theme-dialog-open="d-reseed">Reseed</button>

<dialog class="theme-dialog" id="d-reseed" aria-labelledby="d-reseed-title">
    <form method="dialog">
        <div class="theme-dialog__header">
            <h2 class="theme-dialog__title" id="d-reseed-title">Reseed the instance?</h2>
            <button class="theme-close" value="cancel" aria-label="Close"><theme:icon name="xmark" /></button>
        </div>
        <div class="theme-dialog__body">…</div>
        <div class="theme-dialog__footer">
            <button class="theme-button theme-button--ghost" value="cancel">Cancel</button>
            <button class="theme-button theme-button--danger" value="confirm">Reseed</button>
        </div>
    </form>
</dialog>
```

### Tooltip

A short description of a control that already has a name. The trigger points at
the bubble with `aria-describedby`, which is what a screen reader
announces. The bubble sits above its trigger and is not moved to stay inside
the viewport, so a trigger at the very edge of the viewport is a placement to
avoid.

```html
<span class="theme-tooltip">
    <button class="theme-button theme-button--ghost theme-button--icon" type="button"
            aria-label="Element outlines" aria-describedby="tt-outline">…</button>
    <span class="theme-tooltip__bubble" role="tooltip" id="tt-outline">…</span>
</span>
```

## Headings and text roles

Heading levels four to six have a step of their own. `<h4>` is the body
size at the bold weight; `<h5>` and `<h6>` are smaller, in capitals
and with a wider letter spacing, and `<h6>` is in the secondary text
colour. The titles of the hero and the teaser, which follow the heading level
an editor picks, look the same on every level.

Three text roles are classes rather than elements, so they go on whatever
element the document outline asks for:

| Class | For |
| --- | --- |
| `.theme-display` | The one heading on a landing page that is set larger than a page title. Three sizes: `--1`, `--2` \- the size of the bare class - and `--3`. All three are the size of a page title on a phone and grow to 68, 54 and 43 pixels on a wide window. |
| `.theme-lead` | The paragraph that opens a page. |
| `.theme-eyebrow` | The short label above a heading. |

Rich text needs no class for the rest of the typography:

-   A `<small>` inside a heading is a secondary line, set smaller and
    lighter than the heading: `<h2>Release notes <small>for 2.0</small></h2>`.
-   Headings balance their lines when they wrap, paragraphs avoid a single word
    on the last line.
-   A key combination is a `<kbd>` holding one `<kbd>` per key;
    only the keys are framed.
-   Quotation marks follow the language of the quotation: English by default,
    the German and the French pairs for `lang="de"` and
    `lang="fr"`.
-   German text is hyphenated, where the browser has a German dictionary.
    Other languages are not.
-   Text set right to left with `dir="rtl"` mirrors its indents, list
    markers and quotation rule.

## Layouts of content elements

Several content elements offer a choice of how they look. Each value maps onto
a modifier of the component the element renders, and a value the theme does
not know renders the element without one.

| Content element | Field | Values |
| --- | --- | --- |
| **Text** | **Layout** | **Running text**; **Columns** \- `.theme-text--columns`, two columns of at least 30 characters, one on a phone - see [Feature: Text in columns](../Changelog/2.0/Feature-TextColumns.html#feature-text-columns) |
| **File Links** | **Display file/icon/thumbnail** | The file names alone - `.theme-file-list`; with the icon of the file type - `--icon`; with a thumbnail, or the icon where there is none - `--preview`. See [Feature: File type icons in the file list](../Changelog/2.0/Feature-FileListDisplayTypes.html#feature-file-list-display-types) |
| **Hero**, **Hero, small**, **Hero, text only** | **Layout**, **Eyebrow** | The image at the start - the default; at the end - `.theme-hero--image-end`; centred - `--centred`; a screenshot cut off at the bottom - `--screenshot`; cropped at two edges - `--bordered`. The eyebrow is `.theme-hero__eyebrow`. See [Feature: Layouts and an eyebrow for the heroes](../Changelog/2.0/Feature-HeroLayouts.html#feature-hero-layouts) |
| **Call to action** | **Tone**, **Width** | Surface, accent tint - `.theme-cta--accent`, the other appearance - `--inverse`, placeholder - `--placeholder`; a box - `--boxed`, or a band - `--band`. See [Feature: Call to action content element](../Changelog/2.0/Feature-CallToAction.html#feature-call-to-action) |
| **Testimonial** | **Style** | A rule at the start - the default; a pull quote - `.theme-quote--pull`; centred - `--centred`, both with a quotation mark. See [Feature: Pull quote and centred testimonial style](../Changelog/2.0/Feature-QuotationStyles.html#feature-quotation-styles) |
| **Card group** | **Layout**, **Columns** | A grid - the default; one row that scrolls sideways - `.theme-card-grid--scroller`; a wall of columns the cards stack into - `--wall`, which fills column by column. Two, three or four columns - `--columns-2` to `--columns-4`. See [Feature: Card group, timeline and teaser list](../Changelog/2.0/Feature-CardGroupTimelineTeaserList.html#feature-card-group-timeline-teaser-list) and [Feature: A wall layout for the card group](../Changelog/2.0/Feature-CardWall.html#feature-card-wall) |
| **Split tiles** | **Layout**, and **Tone** per tile | The first image at the start of the line - the default; at the end \- `.theme-split-tiles--reversed`. The side alternates from tile to tile by itself. Each tile takes a tone - `.theme-split-tiles__item--accent`, `--inverse`, `--placeholder`. See [Feature: Carousel and split tiles](../Changelog/2.0/Feature-CarouselAndSplitTiles.html#feature-carousel-and-split-tiles) |
| **Carousel** | **Caption** per slide | Below the image - the default; above it - `.theme-carousel__caption--above`; over its foot - `--overlay`. See [Feature: Carousel and split tiles](../Changelog/2.0/Feature-CarouselAndSplitTiles.html#feature-carousel-and-split-tiles) |

## Tables

The **Table class** of the table content element chooses the look of
the table. The core offers **Striped** and **Bordered**; the
theme adds the rest of what its table component draws:

| Table class | Look |
| --- | --- |
| Default | Rows separated by a hairline, the header row on a tint |
| `striped` | Every other row on the tint |
| `bordered` | A hairline between the columns as well |
| `striped-columns` | Every other column on the tint |
| `hover` | The row under the pointer is highlighted |
| `borderless` | No rules at all |
| `compact` | Half the cell padding |
| `sticky-header` | The header row stays in view while a tall table scrolls |

Each value becomes the modifier `.theme-table--<value>`. A site package
adds its own value with page TSconfig -
`TCEFORM.tt_content.table_class.addItems` \- and styles the class
it produces; `removeItems` hides one of the theme's.

## Alerts

Six kinds, as a modifier of `.theme-alert`. The `role` belongs in
the markup, and it is not the same for every kind:

| Modifier | For | `role` |
| --- | --- | --- |
| `--info` | Information; also the look of the bare class | `status` |
| `--success` | Something finished | `status` |
| `--warning` | Something needs attention | `alert` |
| `--danger` | Something failed | `alert` |
| `--note` | An aside to the text around it | `note`, or none |
| `--tip` | A recommendation. Its colour follows the selected palette. | `note`, or none |

`status` and `alert` are live regions, which announce a change. A
note or a tip does not change, so it is never one.
