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.
type=link fields are not URLs
tx_theme_link and the child table's link column are both TCA
type=link. Their stored value is stdWrap.typolink syntax, not a
bare URL, so every template renders it through f:link.typolink or
f:uri.typolink, never as a plain href. TYPO3 v14's Fluid 5
null-handling change names f:link.typolink as an explicit exception -
it renders through the TypoScript link API rather than building a tag itself
- so no version split was needed to keep this working on both v13.4 and
v14.3.
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.