Feature: #107823 - ComponentFactory for backend components 

See forge#107823

Description 

A new \TYPO3\CMS\Backend\Template\Components\ComponentFactory class has been introduced as the central location for all backend component creation. It provides factory methods for buttons and menu components, offering both pre-configured buttons for common patterns and basic component creation methods.

The ComponentFactory serves multiple purposes:

  1. Pre-configured common buttons – Ready-to-use buttons like back, close, save, reload, and view with standardized icons, labels, and behavior.
  2. Basic button creation – Factory methods for creating button instances (previously available only on ButtonBar).
  3. Menu component creation – Factory methods for creating Menu and MenuItem instances (previously available only on MenuRegistry and Menu).

The deprecated ButtonBar::make*(), Menu::makeMenuItem(), and MenuRegistry::makeMenu() methods have been replaced by ComponentFactory, providing a cleaner separation of concerns where container classes manage organization and ComponentFactory handles component creation.

Additionally, several "add" methods now support fluent interface patterns to enable method chaining for improved code readability.

Database record list and file list 

The \TYPO3\CMS\Backend\RecordList\DatabaseRecordList and \TYPO3\CMS\Filelist\FileList classes, which are responsible in the backend to show all lists of records and files, now make use of the ComponentFactory. The list of "action buttons" is no longer represented with plain HTML and allows a clear distinction of primary and secondary actions.

For this, the new enum \TYPO3\CMS\Backend\Template\Components\ActionGroup and \TYPO3\CMS\Backend\Template\Components\ComponentGroup are also added to the Button API to allow this grouping.

The existing PSR-14 events ( \TYPO3\CMS\Backend\RecordList\Event\ModifyRecordListRecordActionsEvent , \TYPO3\CMS\Filelist\Event\ProcessFileListActionsEvent ) which are used within these classes have been streamlined to deal with these API changes. Details can be found in the related breaking changes document of forge#107884.

The Button API has also been enhanced to allow passing a new \TYPO3\CMS\Backend\Template\Components\Buttons\ButtonSize enum to differentiate the buttons for certain icon sizes.

Available Factory Methods 

The ComponentFactory provides two categories of methods:

Pre-configured common buttons:

These methods provide ready-to-use buttons with sensible defaults. The returned instances are fully mutable and can be further customized using fluent interface methods (for example, setDataAttributes(), setClasses(), setIcon()).

URL parameters accept both string and UriInterface for convenience.

  • createBackButton(string|UriInterface $returnUrl) – Standard back navigation with "Go back" label.
  • createCloseButton(string|UriInterface $closeUrl) – Close button for modal-like views.
  • createSaveButton(string $formName = '') – Standard save button for forms.
  • createReloadButton(string|UriInterface $requestUri) – Reload current view.
  • createViewButton(array $previewDataAttributes = []) – View/preview page button with data attributes.

Basic button creation:

  • createLinkButton() – Creates a new LinkButton instance.
  • createInputButton() – Creates a new InputButton instance.
  • createGenericButton() – Creates a new GenericButton instance.
  • createSplitButton() – Creates a new SplitButton instance.
  • createDropDownButton() – Creates a new DropDownButton instance.
  • createFullyRenderedButton() – Creates a new FullyRenderedButton instance.
  • createShortcutButton() – Creates a new ShortcutButton instance.
  • createDropDownDivider() – Creates a new DropDownDivider instance.
  • createDropDownItem() – Creates a new DropDownItem instance.

Menu component creation:

  • createMenu() – Creates a new Menu instance.
  • createMenuItem() – Creates a new MenuItem instance.

The Button API has also been enhanced to allow passing a new enum to differentiate the buttons for certain icon sizes.

Improvements to Button API types 

The following button types can use getSize() and setSize() methods in their instance to set the icon size with the \TYPO3\CMS\Backend\Template\Components\Buttons\ButtonSize enum, choosing between a small and medium variant (utilizing CSS classes internally):

  • \TYPO3\CMS\Backend\Template\Components\Buttons\DropDownButton
  • \TYPO3\CMS\Backend\Template\Components\Buttons\GenericButton
  • \TYPO3\CMS\Backend\Template\Components\Buttons\LinkButton

Impact 

Backend module developers should now inject ComponentFactory in their controllers to create buttons. The factory provides:

  1. Pre-configured buttons for common patterns (back, close, save, reload, view).
  2. Basic button creation methods (formerly only available on ButtonBar).

The ButtonBar::make*() methods continue to work but are deprecated and will be removed in TYPO3 v15. This change provides a cleaner architecture where ComponentFactory handles all button creation and ButtonBar focuses solely on managing button positioning and organization.

Example – Using pre-configured buttons (inject ComponentFactory):

use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;

// In controller constructor
public function __construct(
    protected readonly ComponentFactory $componentFactory,
) {}

// In controller action
public function editAction(): ResponseInterface
{
    $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();

    // Use pre-configured back button
    $backButton = $this->componentFactory->createBackButton($returnUrl);
    $buttonBar->addButton($backButton, ButtonBar::BUTTON_POSITION_LEFT, 1);

    // Use pre-configured save button
    $saveButton = $this->componentFactory->createSaveButton('editform');
    $buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 2);

    // ...
}
Copied!

Example – Creating basic buttons via ComponentFactory :

use TYPO3\CMS\Backend\Template\Components\ComponentFactory;

// Inject ComponentFactory in constructor
public function __construct(
    protected readonly ComponentFactory $componentFactory,
) {}

// Create basic buttons via factory
$linkButton = $this->componentFactory->createLinkButton()
    ->setHref($url)
    ->setTitle('Custom')
    ->setIcon($icon);
Copied!

Example - Using the ModuleTemplate convenience method:

public function __construct(
    protected readonly ComponentFactory $componentFactory,
) {}

public function myAction(): ResponseInterface
{
    // Shorthand: use ModuleTemplate::addButtonToButtonBar()
    $this->moduleTemplate->addButtonToButtonBar(
        $this->componentFactory->createReloadButton(
            $this->request->getUri()->getPath()
        ),
        ButtonBar::BUTTON_POSITION_RIGHT
    );

    $this->moduleTemplate->addButtonToButtonBar(
        $this->componentFactory->createBackButton($returnUrl),
        ButtonBar::BUTTON_POSITION_LEFT,
        1
    );

    // ...
}
Copied!

Example - Customizing pre-configured buttons:

public function __construct(
    protected readonly ComponentFactory $componentFactory,
) {}

public function myAction(): ResponseInterface
{
    // Pre-configured buttons return mutable instances that can be further
    // customized.
    $reloadButton = $this->componentFactory
        ->createReloadButton(
            (string)$this->uriBuilder->buildUriFromRoute(
                $currentModule->getIdentifier()
            )
        )
        ->setDataAttributes(['csp-reports-handler' => 'refresh']);

    $this->moduleTemplate->addButtonToButtonBar(
        $reloadButton,
        ButtonBar::BUTTON_POSITION_RIGHT
    );

    // Add custom styling or behavior to a save button
    $saveButton = $this->componentFactory
        ->createSaveButton('myform')
        ->setClasses('btn-primary custom-save')
        ->setDataAttributes(['validate' => 'true']);

    $this->moduleTemplate->addButtonToButtonBar(
        $saveButton,
        ButtonBar::BUTTON_POSITION_LEFT
    );

    // URL parameters accept both string and UriInterface
    $backButton = $this->componentFactory->createBackButton(
        $this->request->getUri()
    ); // UriInterface
    // or
    $backButton = $this->componentFactory->createBackButton(
        '/return/url'
    ); // string

    // ...
}
Copied!

Fluent Interface Improvements 

Several "add" methods now support fluent interface patterns, enabling method chaining:

ButtonBar

$buttonBar
    ->addButton($backButton, ButtonBar::BUTTON_POSITION_LEFT, 1)
    ->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
Copied!

Menu

$menu
    ->addMenuItem($listItem)
    ->addMenuItem($gridItem)
    ->addMenuItem($tableItem);
Copied!

MenuRegistry

$menuRegistry
    ->addMenu($viewMenu)
    ->addMenu($filterMenu);
Copied!

These changes provide a more fluent API while maintaining backward compatibility, as the return values were previously ignored ( void).

Design Rationale 

Why fluent interface instead of named parameters?

The ComponentFactory intentionally uses a fluent interface approach (chained method calls) rather than accepting parameters in factory methods. This design decision was made for several important reasons:

Consistency with TYPO3 patterns
The fluent interface pattern is well-established throughout TYPO3's codebase and familiar to extension developers. Introducing a different pattern here would be inconsistent with the rest of the framework.
Diverse button types with different properties
Different button types have vastly different configuration requirements. For example, InputButton needs name/value/form attributes, LinkButton needs href attributes, DropDownButton needs items, and SplitButton needs primary and secondary actions. A unified parameter-based approach does not fit this diversity well and would lead to confusing method signatures with many optional parameters.
Pre-configured buttons solve common cases
The factory already provides pre-configured methods like createSaveButton(), createBackButton(), createCloseButton(), createReloadButton(), and createViewButton() that handle the most common use cases with minimal code.
Avoids duplication and maintenance burden
A parameter-based approach would require duplicating all button-specific configuration knowledge in both the button classes and the factory methods. This creates a maintenance burden where changes to a button's properties must be reflected in multiple locations.
Keeps factory simple and maintainable
By keeping factory methods focused on instantiation, the ComponentFactory remains simple, maintainable, and easy to extend. Each button class maintains complete ownership of its own configuration logic.

Fluent interface is already concise

// Common case – use pre-configured button
$saveButton = $this->componentFactory->createSaveButton('myform');

// Custom case - fluent interface is clear and flexible
$customButton = $this->componentFactory->createInputButton()
    ->setName('custom_action')
    ->setValue('1')
    ->setTitle('Custom Action')
    ->setIcon(
        $this->iconFactory->getIcon('actions-heart', IconSize::SMALL)
    );
Copied!

This design ensures the API remains maintainable, consistent with TYPO3 conventions and suitable for the diverse requirements of different button types while still providing convenience methods for common patterns.