Variants
Basics
Variants allow you to change properties of form elements, validators, and finishers and are activated by conditions. This allows you among other things:
- translating form element values depending on the frontend language
- setting and removing validators of one form element depending on the value of another form element
- hiding entire steps (form pages) depending on the value of a form element
- setting finisher options depending on the value of a form element
- hiding a form element in certain finishers and on the summary step
Variants are defined on the form element level either statically in form definitions or created programmatically through an API. The variants defined within a form definition are automatically applied to the form based on their conditions at runtime. Programmatically, variants can be applied at any time.
Furthermore, conditions of a variant can be evaluated programmatically at any time. However, some conditions are only available at runtime, for example a check for a form element value.
Custom conditions and operators can be added easily.
Only the form element properties listed in a variant are applied to the form element, all other properties are retained. An exception to this rule are finishers and validators. If finishers or validators are not defined within a variant, the original finishers and validators will be used. If at least one finisher or validator is defined in a variant, the originally defined finishers or validators are overwritten by the list of finishers and validators of the variant.
Variants defined within a form definition are all processed and applied in the order of their matching conditions. This means if variant 1 sets the label of a form element to "X" and variant 2 sets the label to "Y", then variant 2 is applied, i.e. the label will be "Y".
Note
At the current state it is not possible to define variants in the UI of the form editor.
Rendering option enabled 
            
    The rendering option 
        enabled is available for all finishers and
all form elements - except the root form element and the first form
page. The option accepts a boolean value (
        true or 
        false).
Setting 
        enabled: true for a form element renders it in the
frontend and enables processing of its value including property mapping
and validation. Setting 
        enabled: false disables the form
element in the frontend. All form elements and finishers except the root form element and the first form page can be enabled
or disabled.
Setting 
        enabled: true for a finisher executes it when
submitting forms. Setting 
        enabled: false skips the finisher.
By default, 
        enabled is set to 
        true.
See examples below to learn more about using this rendering option.
Definition of variants
Variants are defined on the form element level. Check the following - incomplete - example:
type: Text
identifier: text-1
label: Foo
variants:
  -
    identifier: variant-1
    condition: 'traverse(formValues, "checkbox-1") == 1'
    # If the condition matches, the label property of the form
    # element is set to the value 'Bar'
    label: BarAs usual, 
        identifier must be a unique name of the variant on
the form element level.
Each variant has a single 
        condition which applies the variants'
changes as soon as the condition matches. In addition, the remaining
properties are applied to the form element as well. In the
aforementioned example the label of the form element 
        text- is
changed to Bar if the checkbox 
        checkbox- is checked.
The following properties can be overwritten by variants within the
topmost element (
        Form):
- label
- rendering- Options 
- finishers
- renderer- Class - Name 
The following properties can be overwritten by variants within all of the other form elements:
- enabled
- label
- default- Value 
- properties
- rendering- Options 
- validators
Note
To selectively unset list items in variants like select options the special value __ can be used as value for the item to remove.
Conditions
The form framework uses the Symfony component expression language.
Here, an expression is a one-liner that returns a boolean value like
        application. Please check
the Symfony docs
to learn more about this topic. The form framework extends the
expression language with some variables which can be used to access
form values and environment settings.
formRuntime (object) 
            
    You can access every public method from the 
        \TYPO3\,
learn more here.
For example:
        form.
renderable (VariableRenderableInterface) 
            
    
        renderable holds the instance of renderable, the condition
is applied to. This can be used e.g. to access the identifier of the
current renderable without having to duplicate it.
For example:
        traverse.
formValues (array) 
            
    
        form holds all the submitted form element values. Each
key within this array represents a form element identifier.
For example:
        traverse.
stepIdentifier (string) 
            
    
        step is set to the 
        identifier of the current
step.
For example:
        step.
stepType (string) 
            
    
        step is set to the 
        type of the current step.
For example:
        step.
finisherIdentifier (string) 
            
    
        finisher is set to the 
        identifier of the
current finisher or an empty string (while no finishers are executed).
For example:
        finisher.
site (object) 
            
    You can access every public method from 
        \TYPO3\
to access the following important ones:
- getSettings() / The site settings array
- getDefaultLanguage() / The default language object for the current site
- getConfiguration() / The whole configuration of the current site
- getIdentifier() / The identifier for the current site
- getBase() / The base URL for the current site
- getRootPageId() / The ID of the root page of the current site
- getLanguages() / An array of available language for the current site
- getSets() / Configured sets of a site (new in TYPO3 v13+)
For example:
        site.
        site.
More details on the Site object can be found in
Using site configuration in conditions.
siteLanguage (object) 
            
    You can access every public method from 
        \TYPO3\.
The most needed ones are for sure:
- getLanguageId() / Aka sys_language_uid.
- getLocale() / The language locale. Something like 'en_US.UTF-8'.
- getTypo3Language() / The language key for XLF files. Something like 'de' or 'default'.
- getTwoLetterIsoCode() / Returns the ISO-639-1 language ISO code. Something like 'de'.
For example:
        site.
applicationContext (string) 
            
    
        application is set to the application context
(@see GeneralUtility::getApplicationContext()).
For example:
        application.
contentObject (array) 
            
    
        content is set to the data of the current content object
or to an empty array if no content object is available.
For example:
        content.
Working with variants programmatically
Create a variant with conditions through the PHP API:
/** @var TYPO3\CMS\Form\Domain\Model\Renderable\RenderableVariantInterface $variant */
$variant = $formElement->createVariant([
    'identifier' => 'variant-1',
    'condition' => 'traverse(formValues, "checkbox-1") == 1',
    'label' => 'foo',
]);
Get all variants of a form element:
/** @var TYPO3\CMS\Form\Domain\Model\Renderable\RenderableVariantInterface[] $variants */
$variants = $formElement->getVariants();
Apply a variant to a form element regardless of its defined conditions:
$formElement->applyVariant($variant);
Examples
Here are some complex examples to show you the possibilities of the form framework.
Translation of form elements
In this example form elements are translated differently depending on the frontend language.
type: Form
prototypeName: standard
identifier: contact-form
label: Kontaktformular
renderingOptions:
  submitButtonLabel: Senden
variants:
  -
    identifier: language-variant-1
    condition: 'siteLanguage("locale").getName() == "en-US"'
    label: Contact form
    renderingOptions:
      submitButtonLabel: Submit
renderables:
  -
    type: Page
    identifier: page-1
    label: Kontaktdaten
    renderingOptions:
      previousButtonLabel: zurück
      nextButtonLabel: weiter
    variants:
      -
        identifier: language-variant-1
        condition: 'siteLanguage("locale").getName() == "en-US"'
        label: Contact data
        renderingOptions:
          previousButtonLabel: Previous step
          nextButtonLabel: Next step
    renderables:
      -
        type: Text
        identifier: text-1
        label: Vollständiger Name
        properties:
          fluidAdditionalAttributes:
            placeholder: Ihre vollständiger Name
        variants:
          -
            identifier: language-variant-1
            condition: 'siteLanguage("locale").getName() == "en-US"'
            label: Full name
            properties:
              fluidAdditionalAttributes:
                placeholder: Your full nameAdding validators dynamically
In this example a bunch of validators are added to the field
        email- depending on the value of the form element
        checkbox-.
type: Form
prototypeName: standard
identifier: newsletter-subscription
label: Newsletter Subscription
renderables:
  -
    type: Page
    identifier: page-1
    label: General data
    renderables:
      -
        type: Text
        identifier: email-address
        label: Email address
        defaultValue:
        variants:
          -
            identifier: validation-1
            condition: 'traverse(formValues, "checkbox-1") == 1'
            properties:
              fluidAdditionalAttributes:
                required: required
            validators:
              -
                identifier: NotEmpty
              -
                identifier: EmailAddress
      -
        type: Checkbox
        identifier: checkbox-1
        label: Check this and email will be mandatoryHide form elements
In this extensive example the form element 
        email- has
been enabled explicitly but it is fine to leave this out since this is
the default state. The form element 
        text- has been disabled
completely, for example to temporarily remove it from the form. The
field 
        text- is hidden in all finishers and on the summary step.
The 
        Email finisher takes the fact into account that
finishers can refer to form values. It is only enabled if the form
element 
        checkbox- has been activated by the user. Otherwise,
the finisher is skipped.
type: Form
prototypeName: standard
identifier: hidden-field-form
label: Hidden field form
finishers:
  -
    identifier: EmailToReceiver
    options:
      subject: Yes, I am ready
      recipients:
        your.company@example.com: 'Your Company name'
      senderAddress: tritum@example.org
      senderName: tritum@example.org
  -
    identifier: EmailToSender
    options:
      subject: This is a copy of the form data
      recipients:
        {email-address}: '{name}'
      senderAddress: tritum@example.org
      senderName: tritum@example.org
      renderingOptions:
        enabled: '{checkbox-1}'
renderables:
  -
    type: Page
    identifier: page-1
    label: General data
    renderables:
      -
        type: Text
        identifier: text-1
        label: A field hidden on confirmation step and in all mails (finishers)
        variants:
          -
            identifier: hide-1
            renderingOptions:
              enabled: false
            condition: 'stepType == "SummaryPage" || finisherIdentifier in ["EmailToSender", "EmailToReceiver"]'
      -
        type: Text
        identifier: email-address
        label: Email address
        properties:
          fluidAdditionalAttributes:
            required: required
        renderingOptions:
          enabled: true
      -
        type: Text
        identifier: text-3
        label: A temporarily disabled field
        renderingOptions:
          enabled: false
      -
        type: Checkbox
        identifier: checkbox-1
        label: Check this and the sender gets an email
  -
    type: SummaryPage
    identifier: summarypage-1
    label: ConfirmationHide steps
In this example the second step 
        page- is disabled if the field
        checkbox- is checked. Furthermore, the form element
        checkbox- is disabled on the summary step.
type: Form
prototypeName: standard
identifier: multi-step-form
label: Muli step form
renderables:
  -
    type: Page
    identifier: page-1
    label: First step
    renderables:
      -
        type: Text
        identifier: text-1
        label: A field
      -
        type: Checkbox
        identifier: checkbox-1
        label: Check this and the next step will be skipped
        variants:
          -
            identifier: variant-1
            condition: 'stepType == "SummaryPage"'
            renderingOptions:
              enabled: false
  -
    type: Page
    identifier: page-2
    label: Second step
    variants:
      -
        identifier: variant-2
        condition: 'traverse(formValues, "checkbox-1") == 1'
        renderingOptions:
          enabled: false
    renderables:
      -
        type: Text
        identifier: text-2
        label: Another field
  -
    type: SummaryPage
    identifier: summarypage-1
    label: ConfirmationSet finisher values dynamically
In this example finisher values are set differently depending on the application context.
type: Form
prototypeName: standard
identifier: finisher-condition-example
label: Finishers under condition
finishers:
  -
    identifier: Confirmation
    options:
      message: I am NOT a local environment.
variants:
  -
    identifier: variant-1
    condition: 'applicationContext matches "#Production/Local#"'
    finishers:
      -
        identifier: Confirmation
        options:
          message: I am a local environment.
renderables:
  -
    type: Page
    identifier: page-1
    label: General data
    renderables:
      -
        type: Text
        identifier: text-1
        label: A fieldRemove select options
In this example a select option is removed for a specific locale.
type: Form
prototypeName: standard
identifier: option-remove-example
label: Options removed under condition
renderables:
  -
    type: Page
    identifier: page-1
    label: Step
    renderables:
      -
        identifier: salutation
        type: SingleSelect
        label: Salutation
        properties:
          options:
            '': '---'
            mr: Mr.
            mrs: Mrs.
            miss: Miss
        defaultValue: ''
        variants:
          -
            identifier: salutation-variant
            condition: 'siteLanguage("locale").getName() == "zh-CN"'
            properties:
              options:
                miss: __UNSETAdding own expression language providers
If you need to extend the expression language with custom functions you can extend it. For more information check the official docs and the appropriate TYPO3 implementation details.
Register the expression language provider in the extension file
Configuration/. Make sure your expression
language provider implements 
        \Symfony\.
return [
    'form' => [
        Vendor\MyExtension\ExpressionLanguage\CustomExpressionLanguageProvider::class,
    ],
];Adding own expression language variables
If you need to add custom variables to the expression language you can extend it. Then the variables are ready to be checked in conditions.
Register a custom expression language provider as written above and provide the expression language variables:
class CustomExpressionLanguageProvider extends AbstractProvider
{
    public function __construct()
    {
        $this->expressionLanguageVariables = [
            'variableA' => 'valueB',
        ];
    }
}