TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:24 +02:00
commit aad9daaefd
1506 changed files with 94005 additions and 0 deletions
@@ -0,0 +1,173 @@
.. include:: /Includes.rst.txt
.. _howtos-custom-form-element:
==============================
Creating a custom form element
==============================
This tutorial shows you how to create a custom form element for the TYPO3 Form
Framework. We'll create a "Gender Select" element as an example.
.. contents:: Table of Contents
:depth: 2
:local:
Prerequisites
=============
Before you start, make sure you have:
* Basic knowledge of YAML configuration
* A sitepackage where you can add configuration files
Step 1: Create the configuration files
======================================
Create a form set directory in your extension. The sub-directory name is
arbitrary — we use ``CustomElement`` here.
File location
-------------
Create the following structure in your extension:
.. code-block:: none
EXT:my_extension/
Configuration/
Form/
CustomElement/
config.yaml
Configuration structure
-----------------------
Here's the complete configuration for our Gender Select element:
.. literalinclude:: _CustomFormSetup.yaml
:language: yaml
:caption: EXT:my_extension/Configuration/Form/CustomElement/config.yaml (after metadata)
Common inspector editors
~~~~~~~~~~~~~~~~~~~~~~~~
Here are some commonly used inspector editors (:ref:`Inspector <concepts-formeditor-inspector>`) you can add to your form elements:
**Inspector-FormElementHeaderEditor** (100)
Shows the element header in the inspector panel
**Inspector-TextEditor** (200-300)
A simple text input field for properties like label and description
**Inspector-PropertyGridEditor** (400)
A grid editor for managing key-value pairs (like options)
**Inspector-GridColumnViewPortConfigurationEditor** (700)
Controls responsive behavior and column widths for different screen sizes
**Inspector-RequiredValidatorEditor** (800)
Adds a checkbox to make the field required
**Inspector-ValidationErrorMessageEditor** (900)
Allows customizing validation error messages
**Inspector-RemoveElementEditor** (9999)
Shows a button to remove the element from the form
Step 2: Register the configuration
===================================
No PHP or TypoScript registration is needed. TYPO3 discovers YAML files
automatically from any extension that follows the directory convention.
Create a form set with a single :file:`config.yaml`:
.. code-block:: none
EXT:my_extension/
Configuration/
Form/
CustomElement/
config.yaml ← set metadata + all configuration
Set the ``priority`` in :file:`config.yaml` to a value greater than ``10``
(the EXT:form core base set) so your configuration is merged on top:
.. code-block:: yaml
:caption: EXT:my_extension/Configuration/Form/CustomElement/config.yaml
name: my-vendor/custom-element
label: 'My Custom Form Element'
priority: 200
# Form element configuration goes here:
prototypes:
standard:
...
Step 3: Clear Caches
====================
After adding the configuration, you must clear all TYPO3 caches.
Step 4: Using your custom element
==================================
Now you can use your custom element in the form editor:
1. Open the Form Editor user interface (:guilabel:`Forms > [Your Form] > Edit`)
2. Look for "Gender Select" in the form element browser.
3. Add the element to the form.
4. Configure the element using the inspector panel on the right.
5. Save your form.
6. Add a form content element to a page and select the form you just edited.
7. Preview the page in the frontend.
The element will now be available in your forms and will render using the
RadioButton template in the frontend.
Step 5: Customizing frontend output (optional)
===============================================
If you want to use a custom template instead of reusing an existing one, follow
these steps:
Create custom template
----------------------
Create your own Fluid template:
:file:`EXT:my_extension/Resources/Private/Partials/Form/GenderSelect.fluid.html`
.. literalinclude:: _GenderSelect.fluid.html
:language: html
:caption: EXT:my_extension/Resources/Private/Partials/Form/GenderSelect.fluid.html
Update configuration
--------------------
Update your YAML configuration to use the custom partial template:
.. code-block:: yaml
:caption: EXT:my_extension/Configuration/Form/CustomElement/config.yaml
:emphasize-lines: 3-7, 10
prototypes:
standard:
formElementsDefinition:
Form:
renderingOptions:
partialRootPaths:
1732785721: 'EXT:my_extension/Resources/Private/Partials/Form/'
GenderSelect:
renderingOptions:
templateName: 'GenderSelect'
Further reading
===============
* :ref:`Form Configuration <concepts-configuration>`
* :ref:`Register a custom stage template <apireference-formeditor-basicjavascriptconcepts-events-view-inspector-editor-insert-perform>`
@@ -0,0 +1,82 @@
prototypes:
standard:
formElementsDefinition:
GenderSelect:
implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement
renderingOptions:
templateName: 'RadioButton'
properties:
options:
f: 'Female'
m: 'Male'
d: 'Diverse'
formEditor:
label: 'Gender Select'
group: select
groupSorting: 9000
iconIdentifier: form-single-select
editors:
100:
identifier: header
templateName: Inspector-FormElementHeaderEditor
200:
identifier: label
templateName: Inspector-TextEditor
# Labels are retrieved from the default language file "EXT:form/Resources/Private/Language/Database.xlf"
# The most keys follow the pattern: formEditor.elements.FormElement.editor.[identifier].[key]
# In this example: "formEditor.elements.FormElement.editor.label.label"
label: formEditor.elements.FormElement.editor.label.label
propertyPath: label
230:
identifier: elementDescription
templateName: Inspector-TextEditor
label: formEditor.elements.FormElement.editor.elementDescription.label
propertyPath: properties.elementDescription
700:
identifier: gridColumnViewPortConfiguration
templateName: Inspector-GridColumnViewPortConfigurationEditor
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.label
configurationOptions:
viewPorts:
10:
viewPortIdentifier: xs
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xs.label
20:
viewPortIdentifier: sm
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.sm.label
30:
viewPortIdentifier: md
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.md.label
40:
viewPortIdentifier: lg
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.lg.label
50:
viewPortIdentifier: xl
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xl.label
60:
viewPortIdentifier: xxl
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xxl.label
numbersOfColumnsToUse:
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.label
propertyPath: 'properties.gridColumnClassAutoConfiguration.viewPorts.{@viewPortIdentifier}.numbersOfColumnsToUse'
description: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.description
800:
identifier: requiredValidator
templateName: Inspector-RequiredValidatorEditor
label: formEditor.elements.FormElement.editor.requiredValidator.label
validatorIdentifier: NotEmpty
propertyPath: properties.fluidAdditionalAttributes.required
propertyValue: required
configurationOptions:
validationErrorMessage:
label: formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.label
propertyPath: properties.validationErrorMessages
description: formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.description
errorCodes:
10: 1221560910
20: 1221560718
30: 1347992400
40: 1347992453
9999:
identifier: removeButton
templateName: Inspector-RemoveElementEditor
@@ -0,0 +1,105 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
xmlns:formvh="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers"
data-namespace-typo3-fluid="true">
<f:comment>
Custom form element template for a gender selection field.
</f:comment>
<formvh:renderRenderable renderable="{element}">
<f:comment>Render the field wrapper with optional fieldset and legend</f:comment>
<f:render partial="Field/Field"
arguments="{
element: element,
renderFieldset: '{true}',
doNotShowLabel: '{true}'
}"
contentAs="elementContent">
<f:form.validationResults for="{element.rootForm.identifier}.{element.identifier}">
<f:comment>Apply error class if validation errors exist</f:comment>
<f:variable name="errorClass">
{f:if(
condition: '{validationResults.errors}',
then: 'is-invalid'
)}
</f:variable>
<f:comment>Radio button group container</f:comment>
<div id="{element.uniqueIdentifier}"
class="gender-select {errorClass}"
role="radiogroup"
aria-label="{element.label}">
<f:comment>Loop through all available options (e.g., male, female, diverse)</f:comment>
<f:for each="{element.properties.options}" as="label" key="value" iteration="idIterator">
<f:comment>Set ARIA attributes for accessibility</f:comment>
<f:if condition="{element.properties.elementDescription}">
<f:variable name="aria" value="{describedby: '{element.uniqueIdentifier}-desc'}" />
</f:if>
<f:comment>Add error indication to the first radio button if validation fails</f:comment>
<f:if condition="{validationResults.errors} && {idIterator.isFirst}">
<f:variable name="aria" value="{
invalid: 'true',
describedby: '{element.uniqueIdentifier}-errors'
}" />
</f:if>
<f:comment>Individual radio button container</f:comment>
<div class="form-check mb-2">
<label class="form-check-wrapping-label"
for="{element.uniqueIdentifier}-{idIterator.index}">
<f:form.radio
property="{element.identifier}"
id="{element.uniqueIdentifier}-{idIterator.index}"
class="form-check-input"
value="{value}"
errorClass="is-invalid"
additionalAttributes="{formvh:translateElementProperty(
element: element,
property: 'fluidAdditionalAttributes'
)}"
aria="{aria}"
/>
<span class="{element.properties.labelTextClassAttribute}">
{formvh:translateElementProperty(
element: element,
property: '{0: \'options\', 1: value}'
)}
</span>
</label>
</div>
</f:for>
</div>
<f:comment>Display validation errors</f:comment>
<f:if condition="{validationResults.flattenedErrors}">
<span id="{element.uniqueIdentifier}-errors"
role="alert">
<f:for each="{validationResults.errors}" as="error">
<f:format.htmlspecialchars>
{formvh:translateElementError(element: element, error: error)}
</f:format.htmlspecialchars>
<br/>
</f:for>
</span>
</f:if>
</f:form.validationResults>
</f:render>
</formvh:renderRenderable>
</html>