Feature: Theme content elements 

Description 

The theme now ships ten content elements of its own - unlike every element covered in Feature: Core content elements and Feature: Menu content elements, these do not exist in the core at all. Their TCA, their own columns and a shared inline child table all belong to this extension:

Content element Renders through
Hero A full hero: heading, text, media and call-to-action links.
Hero, small The same, reduced.
Hero, text only The same, without media.
Teaser A short text teaser without media.
Media teaser Text placed beside a single image.
Media teaser grid Several media teasers arranged in a grid.
Testimonial A quotation with its attribution.
Author A person: portrait, name, role and links.
Link list A list of links.
Social links The same, labelled instead of rendered as icons.

All ten are grouped under their own Theme entry in the Create new content element wizard, so an editor can tell them apart from the core set at a glance. No page TSconfig registers that group - since TYPO3 v13 the wizard is generated straight from the label, description, group and icon already given to ExtensionManagementUtility::addRecordType().

Naming 

CTypes are prefixed theme_, columns tx_theme_, and the shared inline child table is tx_theme_list_item - short rather than the full extension key, because themeextensiondevelopment_hero is unusable in a showitem string and in TypoScript. This follows the reference implementation's own equally short prefix for the identical reason, and it accepts the same collision risk deliberately: another extension is free to also prefix its own fields theme_.

No schema of its own 

This extension ships no ext_tables.sql. The whole schema - the four tx_theme_* columns added to tt_content and every column of tx_theme_list_item - is derived from TCA by TYPO3CMSCoreDatabaseSchemaDefaultTcaSchema::enrich(), on both supported core versions.

One column needed to be declared explicitly rather than left to that derivation. An inline relation's foreign_field and foreign_table_field are auto-created on the child table if not already present; a field used only in foreign_match_fields - here, the column that records which of the four inline relations sharing this child table a row belongs to - is not part of that special case and gets no column for free. fieldname on tx_theme_list_item is therefore a real type=input column, the same way the core's own sys_file_reference declares its own fieldname for the identical reason.

Inline children, and the item.data trap 

No core data processor resolves a generic database relation the way FilesProcessor resolves FAL - that class only ever wraps FileCollector, which is FAL-specific by construction. Author, Link list, Social links and Media teaser grid all resolve their shared inline relation with TYPO3CMSFrontendDataProcessingDatabaseQueryProcessor instead.

That processor wraps every row as ['data' => $record] - the same wrapping Feature: Menu content elements already documents for Categorized content. Every template reads item.data.link, never item.link: the latter resolves to nothing and renders an empty list with no error, not a broken one. This is not a hypothetical risk here either - it happened during this element set's own development, before the functional test suite caught it.

GalleryProcessor is deliberately not used 

Hero, Hero, small, Media teaser and Author all resolve their image with FilesProcessor alone. Each shows exactly one image in a fixed-shape box, and none of their forms expose Columns, Orientation or the other gallery fields GalleryProcessor reads - wiring it in would bind the layout to columns an editor can never set.

Markup 

Link list and Social links reuse the existing .theme-content-menu component - the same one every menu_* element uses - rather than a list component of their own: structurally the shape is identical, and a purpose-built list component would only duplicate styling that already exists. Author's own profile/contact links reuse the identical pair for the same reason.

Only one new component was needed: .theme-author - a portrait, a role line and a bio. It does not render the person's own name; that goes through the shared content-element heading like every other element, so .theme-author sits below it rather than repeating it.

Known gaps 

This theme ships no icon assets and no icon component. Social links therefore renders the same text-label list as Link list - link_label stands in for a platform icon, not a glyph approximating one.

.theme-hero__eyebrow exists in the hero component's stylesheet, but no hero variant's TCA offers an eyebrow field to back it - omitted rather than invented.

A field was removed 

Testimonial originally exposed the core Images field, the same way Hero and Author do. The quote component it renders through has no media slot at all, so a filled-in image would never have appeared on the page - an editor attaches a portrait and the work is silently gone. The field was removed from the form rather than left inert.

Impact 

An installation using the theme can create all ten elements from its own wizard group and gets working output for every one of them. Tests/Functional/ThemeContentElementRenderingTest.php renders a page carrying one of each and asserts every element reaches the content-element wrapper, that the inline-relation based elements actually list their children (not merely an empty, correct-looking wrapper), that inline children keep the order an editor gave them, and that the button variant and the link field resolve to a real, followable URL rather than a raw t3:// reference.

See docs/architecture/content-elements.md in the developer documentation for the full CType table, the schema derivation details, and the reasoning behind each decision summarised above.