05 / Blog · 02.10.2026

A Component Needs a Contract, Not Just a Preview

A living style guide only becomes an architectural tool when components use the same templates, defined inputs and verifiable exports as the application. For Fluid, this creates a contract that holds design, implementation and testing together.

Preview and production component are connected by one shared verified contract.

The preview can be correct and still lie

A component explorer quickly conveys a reassuring picture: buttons, cards and navigation elements are neatly arranged side by side, variants can be selected and responsive widths tried out. Yet the actual TYPO3 page may look different. Perhaps the style guide uses recreated HTML, outdated sample data or its own rendering environment. Perhaps a variant exists only in the documentation, while the production template has long expected different arguments.

In that case, the preview is not a reliable description of the system, but a second implementation. The more attractive it looks, the easier it is to overlook this difference.

A robust living style guide should therefore not ask: “How can we display the component as similarly as possible?” The better question is: “What contract can the preview, TYPO3 runtime, tests and external tools use together?”

This contract consists of more than HTML. It encompasses the public component API, controlled sample data, rendering conditions, states and a versionable export format.

The Fluid template is the executable source

The obvious safeguard against divergence is for the preview and application to render the same template. For Fluid components, this is now particularly tangible: components define their inputs with <f:argument> and accept nested content through <f:slot>. The TYPO3 documentation describes these arguments as a typed API and, since TYPO3 14, recommends configuration-based Component Collections over the previous class-based registration. (Fluid in TYPO3 14.3)

A small component might begin like this:

<f:argument name="title" type="string" />
<f:argument name="variant" type="string" optional="{true}" default="default" />
<f:argument name="url" type="string" optional="{true}" />

<article class="card card--{variant}">
    <h2>{title}</h2>
    <f:slot />
</article>

Even these few lines contain more architecture than an isolated HTML demo:

  • title is required and defined as a string.
  • variant is optional and has a defined default value.
  • url is part of the public API, even if its specific handling still needs to be verified.
  • The default slot permits content without fully prescribing its structure.

The style guide should read these declarations and derive its controls and validation from them. A separately maintained list of props would merely be another potential source of divergence.

This does not mean that every business rule can be extracted directly from the template. A string type, for example, does not specify which variants are permitted or whether a URL must remain internal. Such restrictions require supplementary metadata or tests. What matters is that there are not multiple equally authoritative truths for the same information.

Strict inputs make changes visible

Components are particularly maintainable when unknown arguments are not silently accepted. A typo such as varaint="compact" should not result in an apparently normal default card. It should be detected during rendering, analysis or, at the latest, in the pipeline.

When registering a Component Collection, TYPO3 explicitly allows configuration of whether additional arguments are permitted. For public, reusable components, a closed contract is usually the safer default. Openness can be useful, for example for intentionally passed-through HTML attributes; however, it should be a documented decision rather than a matter of convenience.

A change to the API also needs a recognisable meaning:

new optional argument          → generally compatible
new variant                    → compatible, but requires visual review
changed default value          → potentially breaking presentation
renamed required argument      → breaking change
removed slot                   → breaking structural change
changed HTML semantics         → potentially breaking for CSS, JS and accessibility

Semantic versioning alone does not resolve these questions. But it forces changes to be considered as changes to a contract, not merely as a template diff.

Slots are controlled extension points

Slots solve a genuine reuse problem: not every type of content can be usefully reduced to simple arguments. A teaser can contain text, media or an action group without the component having to know every possible child structure.

This flexibility has a limit. A slot is not carte blanche for arbitrary dependencies on the outer rendering context. Fluid components operate with their own context; ViewHelpers that look for a parent ViewHelper may therefore fail at component boundaries. The current TYPO3 reference cites form fields as a specific example and recommends passing the actual field through a slot into a presentational component. (TYPO3 Form ViewHelper 14.3)

This provides a useful architectural separation:

  • The component is responsible for labels, spacing, error presentation and layout.
  • The caller is responsible for the state-bound form field.
  • The slot connects the two without pretending to provide the form contract.

Named slots should be documented like arguments. Each slot needs at least a purpose, expected content and defined behaviour when it is not populated. Otherwise, its apparent flexibility will later be constrained by implicit CSS and JavaScript assumptions.

Variants are examples, not parallel templates

A variant should demonstrate a relevant state, not copy the component. Typical variants show:

  • short and very long content,
  • missing optional media,
  • different languages,
  • empty or maximum-length lists,
  • focus, error and disabled states,
  • narrow and wide containers,
  • light and dark colour schemes.

Each variant needs a reproducible context. This context should be small, understandable and free of production data. A complete database export makes a preview neither portable nor easy to verify.

A declarative model that serves only the public API is useful:

{
  "component": "Molecule.TeaserCard",
  "variant": "long-title-without-image",
  "arguments": {
    "title": "An intentionally long heading for a narrow container",
    "variant": "highlighted"
  },
  "slots": {
    "default": "<p>Controlled sample content.</p>"
  },
  "viewport": {
    "width": 360,
    "height": 640
  }
}

It must remain clear which slot content is considered trustworthy and how it is sanitised. A style guide must not become an HTML execution surface by allowing editable sample content to enter the preview unfiltered.

A preview requires defined runtime conditions

Using the same template is not enough if the environment and dependencies remain different. Font files, CSS, JavaScript, translations, asset paths and ViewHelpers influence the result just as much as the arguments do.

Every preview should therefore make its runtime contract visible:

  1. Which Fluid and TYPO3 version is used?
  2. Which Component Collections and namespaces are registered?
  3. Which stylesheets and scripts are loaded?
  4. Which language, locale and writing direction apply?
  5. Which base URL is used for assets?
  6. Which global variables are available?
  7. Which dependencies are deliberately unsupported?

A standalone Fluid runtime is valuable because it can render components quickly and without a complete CMS. However, it only proves the independent core. A TYPO3 fixture remains necessary when integration, namespaces, asset resolution, or backend and frontend routes form part of the promise.

The test pyramid for a style guide should therefore not consist of a single screenshot test. Parser and rendering tests can verify the core quickly. Integration tests demonstrate registration in TYPO3. Browser tests examine interaction, keyboard operation, responsive previews and real assets.

Screenshots are evidence, but not a contract

Visual regression tests are useful when applied selectively. They detect shifted spacing, clipped content and unintended colour changes. However, a screenshot does not explain why a change is permitted or wrong. It can also be unstable due to font rendering, the operating system and the browser environment.

Visual comparisons should therefore be based on named variants and controlled runtime conditions. Every discrepancy requires a professional review, not merely a blanket update of all reference images.

In addition to screenshots, structural tests should answer:

  • Does the component render with all required arguments?
  • Are unknown arguments rejected?
  • Does the heading and landmark structure remain correct?
  • Can the variant be operated using a keyboard?
  • Are unsafe links or documentation content rejected?
  • Does every documented variant actually exist?
  • Are exported files accessible through the manifest?

The screenshot shows a specific presentation. The structured contract explains which behaviour must be preserved.

An export is a public interface

Once components leave the style guide, a collection of saved HTML files is no longer sufficient. Other tools need to know which file belongs to which component, the context in which it was generated and which version of the format it uses.

A portable export can therefore connect several artefacts:

manifest.json
components/
  molecule-teaser-card/
    default.html
    default.preview.json
    TeaserCard.fluid.html
    README.md
schema/
  manifest.schema.json
  preview.schema.json

The manifest is the entry point. It contains stable identifiers, variants, relative paths, the format version and, where applicable, checksums. Paths must be normalised and protected against traversal. An importer must never resolve an entry such as ../../outside.html outside the export directory.

JSON Schema is suitable for describing structure, required fields and permitted values in a machine-readable form. The official specification page continues to list Draft 2020-12 as the current version; the schema can validate exports in the pipeline and assist consumers with implementation. (JSON Schema Specification)

The direction of the dependency is important: the schema describes the export contract. It should not expose internal PHP classes or incidental filesystem details. Otherwise, a portable format becomes a reflection of the current implementation.

Compatibility belongs in the pipeline

A pipeline can verify the component contract in several steps:

  1. All templates are discovered and uniquely named.
  2. Argument and slot declarations can be read.
  3. Every variant references an existing component.
  4. Variants provide all required arguments and no unknown inputs.
  5. The components render in the independent runtime.
  6. A real TYPO3 fixture verifies the integration.
  7. Structural, accessibility and selected visual tests run in the browser.
  8. The static export is generated and validated against its schemas.
  9. All manifest paths are resolved again in a fresh directory.

Current TYPO3 versions also support this idea during development: since TYPO3 14.2, fluid:schema:generate can generate XSD files for available Fluid components and thus enable autocompletion in supported IDEs. This requires the Collection to be able to enumerate its components; TYPO3's own integration supports this directly. (TYPO3 Feature #109114)

This makes the explicit component API useful even before it reaches the style guide. It already improves the moment when a component is used in a Fluid template.

TYPO3 Fractal as a concrete implementation

According to the project information provided, TYPO3 Fractal follows precisely this separation between core and integration. The independent PHP core uses typo3fluid/fluid, while adapters provide integration with TYPO3 13 and 14. A DDEV fixture verifies the generated files through real TYPO3 routes and in the browser.

The runtime scanner discovers .fluid.html templates. <f:argument> and <f:slot> declarations become editable controls, while variants follow a naming convention. The preview runs in a sandbox iframe. According to the project description, supplementary Markdown documentation is processed conservatively; unsafe URLs and arbitrary HTML are not included.

The export is also designed as a contract: browser-ready HTML, preview JSON, the Fluid source template and optional documentation are connected by manifest.json. A JSON Schema file allows consumers to perform validation, while relative paths are protected against traversal.

These are characteristics of the project described. It does not follow that every TYPO3 installation needs exactly this format or tool. The general lesson is simpler: if a component library is intended to serve multiple runtimes and consumers, it needs an explicit, verifiable boundary.

The crucial questions before release

Before publishing or changing a component, several questions should have clear answers:

  • Does the preview render the actual Fluid template?
  • Are required arguments, optional arguments and default values explicit?
  • Are unknown inputs rejected or deliberately permitted?
  • Are slots documented extension points rather than hidden context dependencies?
  • Does every variant have a small, reproducible sample data set?
  • Are language, assets, theme and viewport controlled?
  • Is there a real TYPO3 integration in addition to the standalone test?
  • Do tests verify structure and behaviour, not just pixels?
  • Is a static export versioned and validatable against a schema?
  • Can all manifest paths be resolved safely in a fresh directory?
  • Is a breaking API change treated as such?

Not every component requires the same depth of testing. A decorative divider has a smaller contract than navigation or a multi-step form. Yet a clear basic structure is worthwhile even for simple building blocks: it prevents many small exceptions from growing into an unmaintainable system.

The style guide is a consumer of the system

A living style guide should not stand above the application and impose a second component world upon it. It is a particularly demanding consumer of the same contracts: it must discover templates, understand inputs, create controlled states and make results portable.

This perspective changes the measure of quality. What matters is not how complete the gallery appears. What matters is whether a change to arguments, slots, variants or exports visibly fails there before spreading unnoticed into multiple projects.

A good preview shows a component. A good contract ensures that everyone involved is actually talking about the same component.