Define form elements from SDC [#3508641] | Drupal.org
Skip to search
Can we use first and third party cookies and web beacons to
understand our audience, and to tailor promotions you see
Define form elements from SDC
Patch (to be ported)
Project:
Drupal core
Version:
11.x-dev
Component:
single-directory components
Priority:
Normal
Category:
Feature request
Assigned:
Unassigned
Issue tags:
ddd2025
Chicago2026
DevDaysAthens2026
Reporter:
pdureau
Created:
24 Feb 2025 at 10:38 UTC
Updated:
24 Apr 2026 at 12:37 UTC
Jump to comment:
Most recent
Most recent file
Problem/Motivation
In
#3494634: Compatibility between SDC and the Form API
, we discussed 2 incompatibilities between SDC and the form API:
we can put a full form into a component slot, but we can't put a form element of a form defined outside the component
we can't define form element, directly usable with the Form API, with SDC. So we can't easily implement some design systems components like
Let's deal with the second point in this dedicated ticket.
Proposed resolution
Form properties to evaluate:
#ajax: (array) Array of elements to specify Ajax behavior. See the Javascript API and AJAX Forms guides for more information.
#default_value: Default value for the element. See also #value.
#description: (string) Help or description text for the element. In an ideal user interface, the #title should be enough to describe the element, so most elements should not have a description; if you do need one, make sure it is translated. If it is not already wrapped in a safe markup object, it will be filtered for XSS safety.
#description_display: (string) Where and how to display the #description.
#disabled: (bool) If TRUE, the element is shown but does not accept user input.
#prefix: (string) Prefix to display before the HTML input element. Should be translated, normally. If it is not already wrapped in a safe markup object, will be filtered for XSS safety.
#suffix: (string) Suffix to display after the HTML input element. Should be translated, normally. If it is not already wrapped in a safe markup object, will be filtered for XSS safety.
#required: (bool) Whether or not input is required on the element.
#required_error: (string) Override default error message "@field_title is required" will be used if this is undefined.
#title: (string) Title of the form element. Should be translated.
#title_display: (string) Where and how to display the #title.
#value: Used to set values that cannot be edited by the user
The other ones are internal, or not self-contained, or needs PHP. That means form elements created from SDC doesn't have:
#after_build: (array) Array of callables or function names, which are called after the element is built. Arguments: $element, $form_state.
#element_validate: (array) Array of callables or function names, which are called to validate the input. Arguments: $element, $form_state, $form.
#process: (array) Array of callables or function names, which are called during form building. Arguments: $element, $form_state, $form.
#value_callback: (callable) Callable or function name, which is called to transform the raw user input to the element's value. Arguments: $element, $input, $form_state.
...
This will not be a missing feature, but a welcomed feature, however:
If really needed by the front developer, to implement UI logic, we can imagine so declarative ways:
inline twig transformation for
#process
json schema for
#element_validate
...
If a back developer want to add some callbacks, it will be a applicative/business need. The form elements that will be created will be alterable like any other form elements.
So, it will be the opportunity to split UI logic from business logic, which is not currently the case in the Form API.
How do we create form elements (which are plugins too) from those SDC plugins? Plugin derivatives?
Remaining tasks
Let's start by trying to re-implement a few Core form element with SDC, some simple, some complex:
Textfield
Checkboxes
Checkbox
Actions
Submit button
...
Out of scope
The scope of this ticket is not to override or replace existing form element but to create new form elements, aside the existing ones, from SDC. We hope one day all Core form elements will be defined as SDC components, but it will be other issues.
Also, we don't address the need of derivative configurable plugins like Field Widgets or WebformElement. It may be the purpose of some contrib modules.
Comment
File
Size
Author
#37
3508641-nr-bot_cvdym73b.txt
2.39 KB
needs-review-queue-bot
#29
3508641-nr-bot_g13hb1kn.txt
91 bytes
needs-review-queue-bot
#12
Peek 20-04-2025 18-33.gif
4.03 MB
grimreaper
Issue fork
drupal-3508641
Show commands
Start within a Git clone of the project using the
version control instructions
Add & fetch this issue forkβs repository
Or,
if you do not have
SSH keys set up on git.drupalcode.org
Add & fetch this issue forkβs repository
3508641-define-form-elements
plain diff
MR
!11876
Check out this branch for the first time
Check out existing branch, if you already have it locally
About issue forks
Comments
Comment
#1
24 February 2025 at 10:38
pdureau
created an issue. See
original summary
or
to post comments
Comment
#2
grimreaper
French
France π«π·
commented
24 February 2025 at 10:39
Assigned:
Unassigned
grimreaper
or
to post comments
Comment
#3
grimreaper
French
France π«π·
commented
24 February 2025 at 10:41
Issue summary:
View changes
or
to post comments
Comment
#4
grimreaper
French
France π«π·
commented
24 February 2025 at 10:49
Issue summary:
View changes
or
to post comments
Comment
#5
grimreaper
French
France π«π·
commented
24 February 2025 at 10:57
Proposed resolution:
In the YAML, a new "form" root level entry to contain those form "props" (ajax, description, etc.).
Or
Normal props declaration, and a mapping system to say "this 'message' prop/slot is sourced by 'description' form property".
or
to post comments
Comment
#6
pdureau
commented
30 March 2025 at 17:01
Let's have a look on all 37 Render Elements from Core:
$ grep -r "\[FormElement(" . | sort | grep -v test
./lib/Drupal/Core/Datetime/Element/Datelist.php:#[FormElement('datelist')]
./lib/Drupal/Core/Datetime/Element/Datetime.php:#[FormElement('datetime')]
./lib/Drupal/Core/Entity/Element/EntityAutocomplete.php:#[FormElement('entity_autocomplete')]
./lib/Drupal/Core/Render/Element/Button.php:#[FormElement('button')]
./lib/Drupal/Core/Render/Element/Checkboxes.php:#[FormElement('checkboxes')]
./lib/Drupal/Core/Render/Element/Checkbox.php:#[FormElement('checkbox')]
./lib/Drupal/Core/Render/Element/Color.php:#[FormElement('color')]
./lib/Drupal/Core/Render/Element/Date.php:#[FormElement('date')]
./lib/Drupal/Core/Render/Element/Email.php:#[FormElement('email')]
./lib/Drupal/Core/Render/Element/File.php:#[FormElement('file')]
./lib/Drupal/Core/Render/Element/Hidden.php:#[FormElement('hidden')]
./lib/Drupal/Core/Render/Element/ImageButton.php:#[FormElement('image_button')]
./lib/Drupal/Core/Render/Element/Item.php:#[FormElement('item')]
./lib/Drupal/Core/Render/Element/LanguageSelect.php:#[FormElement('language_select')]
./lib/Drupal/Core/Render/Element/MachineName.php:#[FormElement('machine_name')]
./lib/Drupal/Core/Render/Element/Number.php:#[FormElement('number')]
./lib/Drupal/Core/Render/Element/PasswordConfirm.php:#[FormElement('password_confirm')]
./lib/Drupal/Core/Render/Element/Password.php:#[FormElement('password')]
./lib/Drupal/Core/Render/Element/PathElement.php:#[FormElement('path')]
./lib/Drupal/Core/Render/Element/Radio.php:#[FormElement('radio')]
./lib/Drupal/Core/Render/Element/Radios.php:#[FormElement('radios')]
./lib/Drupal/Core/Render/Element/Range.php:#[FormElement('range')]
./lib/Drupal/Core/Render/Element/Search.php:#[FormElement('search')]
./lib/Drupal/Core/Render/Element/Select.php:#[FormElement('select')]
./lib/Drupal/Core/Render/Element/Submit.php:#[FormElement('submit')]
./lib/Drupal/Core/Render/Element/Table.php:#[FormElement('table')]
./lib/Drupal/Core/Render/Element/Tableselect.php:#[FormElement('tableselect')]
./lib/Drupal/Core/Render/Element/Tel.php:#[FormElement('tel')]
./lib/Drupal/Core/Render/Element/Textarea.php:#[FormElement('textarea')]
./lib/Drupal/Core/Render/Element/Textfield.php:#[FormElement('textfield')]
./lib/Drupal/Core/Render/Element/Token.php:#[FormElement('token')]
./lib/Drupal/Core/Render/Element/Url.php:#[FormElement('url')]
./lib/Drupal/Core/Render/Element/Value.php:#[FormElement('value')]
./lib/Drupal/Core/Render/Element/VerticalTabs.php:#[FormElement('vertical_tabs')]
./lib/Drupal/Core/Render/Element/Weight.php:#[FormElement('weight')]
./modules/file/src/Element/ManagedFile.php:#[FormElement('managed_file')]
./modules/language/src/Element/LanguageConfiguration.php:#[FormElement('language_configuration')]
The "inside" of form elements
23 of them are direct calls to theme hooks
$ grep -r -A 1000 "\[FormElement(" . | grep '#theme..=' | sort | grep -v test
./lib/Drupal/Core/Datetime/Element/Datelist.php- '#theme' => 'datetime_form',
./lib/Drupal/Core/Datetime/Element/Datetime.php- '#theme' => 'datetime_form',
./lib/Drupal/Core/Render/Element/Checkbox.php- '#theme' => 'input__checkbox',
./lib/Drupal/Core/Render/Element/Color.php- '#theme' => 'input__color',
./lib/Drupal/Core/Render/Element/Date.php- '#theme' => 'input__date',
./lib/Drupal/Core/Render/Element/Email.php- '#theme' => 'input__email',
./lib/Drupal/Core/Render/Element/File.php- '#theme' => 'input__file',
./lib/Drupal/Core/Render/Element/Hidden.php- '#theme' => 'input__hidden',
./lib/Drupal/Core/Render/Element/MachineName.php- '#theme' => 'input__textfield',
./lib/Drupal/Core/Render/Element/Number.php- '#theme' => 'input__number',
./lib/Drupal/Core/Render/Element/Password.php- '#theme' => 'input__password',
./lib/Drupal/Core/Render/Element/Radio.php- '#theme' => 'input__radio',
./lib/Drupal/Core/Render/Element/Range.php- '#theme' => 'input__range',
./lib/Drupal/Core/Render/Element/Search.php- '#theme' => 'input__search',
./lib/Drupal/Core/Render/Element/Select.php- '#theme' => 'select',
./lib/Drupal/Core/Render/Element/Table.php- '#theme' => 'table',
./lib/Drupal/Core/Render/Element/Tableselect.php- '#theme' => 'table__tableselect',
./lib/Drupal/Core/Render/Element/Tel.php- '#theme' => 'input__tel',
./lib/Drupal/Core/Render/Element/Textarea.php- '#theme' => 'textarea',
./lib/Drupal/Core/Render/Element/Textfield.php- '#theme' => 'input__textfield',
./lib/Drupal/Core/Render/Element/Token.php- '#theme' => 'input__hidden',
./lib/Drupal/Core/Render/Element/Url.php- '#theme' => 'input__url',
./modules/file/src/Element/ManagedFile.php- '#theme' => 'file_link',
./modules/file/src/Element/ManagedFile.php- '#theme' => 'file_managed_file',
file_managed_file:
{{ element }}
input.html.twig:
{{ children }}
For many render elements with a template suggestion system, but suggested templates are not used.
With template_preprocess_input in form.inc
textarea.html.twig:
With template_preprocess_textarea in form.inc
select.html.twig:
With template_preprocess_select in form.inc.
datetime-form.html.twig:
{{ content }}
5 are using
#theme_wrappers
instead
./lib/Drupal/Core/Render/Element/Button.php- '#theme_wrappers' => ['input__submit'],
./lib/Drupal/Core/Render/Element/Checkboxes.php- '#theme_wrappers' => ['checkboxes'],
./lib/Drupal/Core/Render/Element/ImageButton.php- '#theme_wrappers' => ['input__image_button'],
./lib/Drupal/Core/Render/Element/Radios.php- '#theme_wrappers' => ['radios'],
./lib/Drupal/Core/Render/Element/VerticalTabs.php- '#theme_wrappers' => ['vertical_tabs', 'form_element'],
checkboxes.html.twig:
With template_preprocess_checkboxes in form.inc
radios.html.twig:
With template_preprocess_radios in form.inc
vertical-tabs.html.twig:
With template_preprocess_vertical_tabs in form.inc
Other form elements
The other 10 (9?) are:
./lib/Drupal/Core/Entity/Element/EntityAutocomplete.php:#[FormElement('entity_autocomplete')]
./lib/Drupal/Core/Render/Element/Item.php:#[FormElement('item')]
./lib/Drupal/Core/Render/Element/LanguageSelect.php:#[FormElement('language_select')]
./lib/Drupal/Core/Render/Element/MachineName.php:#[FormElement('machine_name')]
./lib/Drupal/Core/Render/Element/PathElement.php:#[FormElement('path')]
./lib/Drupal/Core/Render/Element/PasswordConfirm.php:#[FormElement('password_confirm')]
./lib/Drupal/Core/Render/Element/Submit.php:#[FormElement('submit')]
./lib/Drupal/Core/Render/Element/Value.php:#[FormElement('value')]
./lib/Drupal/Core/Render/Element/Weight.php:#[FormElement('weight')]
./modules/language/src/Element/LanguageConfiguration.php:#[FormElement('language_configuration')]
How are they built? Do they have templates?
Wrappers
2 have custom wrapper
./lib/Drupal/Core/Datetime/Element/Datelist.php- '#theme_wrappers' => ['datetime_wrapper'],
./lib/Drupal/Core/Datetime/Element/Datetime.php- '#theme_wrappers' => ['datetime_wrapper'],
datetime-wrapper.html.twig:
{%
set title_classes = [
required ? 'js-form-required',
required ? 'form-required',
%}
{% if title %}
{{ title }}
{% endif %}
{{ content }}
{% if errors %}
{{ errors }}
{% endif %}
{% if description %}
{{ description }}
{% endif %}
19 are using standard form_element wrapper
./lib/Drupal/Core/Render/Element/Checkbox.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Color.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Date.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Email.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/File.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Item.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/MachineName.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Number.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/PasswordConfirm.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Password.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Radio.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Search.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Select.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Tel.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Textarea.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Textfield.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/Url.php- '#theme_wrappers' => ['form_element'],
./modules/file/src/Element/ManagedFile.php- '#theme_wrappers' => ['form_element'],
./lib/Drupal/Core/Render/Element/VerticalTabs.php- '#theme_wrappers' => ['vertical_tabs', 'form_element'],
form-element.html.twig:
{%
set classes = [
'js-form-item',
'form-item',
'js-form-type-' ~ type|clean_class,
'form-item-' ~ name|clean_class,
'js-form-item-' ~ name|clean_class,
title_display not in ['after', 'before'] ? 'form-no-label',
disabled == 'disabled' ? 'form-disabled',
errors ? 'form-item--error',
%}
{%
set description_classes = [
'description',
description_display == 'invisible' ? 'visually-hidden',
%}
{% if label_display in ['before', 'invisible'] %}
{{ label }}
{% endif %}
{% if prefix is not empty %}
{{ prefix }}
{% endif %}
{% if description_display == 'before' and description.content %}
{{ description.content }}
{% endif %}
{{ children }}
{% if suffix is not empty %}
{{ suffix }}
{% endif %}
{% if label_display == 'after' %}
{{ label }}
{% endif %}
{% if errors %}
{% endif %}
{% if description_display in ['after', 'invisible'] and description.content %}
{{ description.content }}
{% endif %}
What about the 16 (17?) others?
./lib/Drupal/Core/Entity/Element/EntityAutocomplete.php:#[FormElement('entity_autocomplete')]
./lib/Drupal/Core/Render/Element/Button.php:#[FormElement('button')]
./lib/Drupal/Core/Render/Element/Checkboxes.php:#[FormElement('checkboxes')]
./lib/Drupal/Core/Render/Element/Hidden.php:#[FormElement('hidden')]
./lib/Drupal/Core/Render/Element/ImageButton.php:#[FormElement('image_button')]
./lib/Drupal/Core/Render/Element/LanguageSelect.php:#[FormElement('language_select')]
./lib/Drupal/Core/Render/Element/PathElement.php:#[FormElement('path')]
./lib/Drupal/Core/Render/Element/Radios.php:#[FormElement('radios')]
./lib/Drupal/Core/Render/Element/Range.php:#[FormElement('range')]
./lib/Drupal/Core/Render/Element/Submit.php:#[FormElement('submit')]
./lib/Drupal/Core/Render/Element/Table.php:#[FormElement('table')]
./lib/Drupal/Core/Render/Element/Tableselect.php:#[FormElement('tableselect')]
./lib/Drupal/Core/Render/Element/Token.php:#[FormElement('token')]
./lib/Drupal/Core/Render/Element/Value.php:#[FormElement('value')]
./lib/Drupal/Core/Render/Element/Weight.php:#[FormElement('weight')]
./modules/file/src/Element/ManagedFile.php:#[FormElement('managed_file')]
./modules/language/src/Element/LanguageConfiguration.php:#[FormElement('language_configuration')]
Processing
20 have a custom valueCallback()
$ grep -r -A 1000 "\[FormElement(" . | grep ' valueCallback' | sort | grep -v test | awk '{print $1}'
./lib/Drupal/Core/Datetime/Element/Datelist.php-
./lib/Drupal/Core/Datetime/Element/Datetime.php-
./lib/Drupal/Core/Entity/Element/EntityAutocomplete.php-
./lib/Drupal/Core/Render/Element/Checkboxes.php-
./lib/Drupal/Core/Render/Element/Checkbox.php-
./lib/Drupal/Core/Render/Element/File.php-
./lib/Drupal/Core/Render/Element/ImageButton.php-
./lib/Drupal/Core/Render/Element/MachineName.php-
./lib/Drupal/Core/Render/Element/PasswordConfirm.php-
./lib/Drupal/Core/Render/Element/Password.php-
./lib/Drupal/Core/Render/Element/PathElement.php-
./lib/Drupal/Core/Render/Element/Radios.php-
./lib/Drupal/Core/Render/Element/Range.php-
./lib/Drupal/Core/Render/Element/Select.php-
./lib/Drupal/Core/Render/Element/Table.php-
./lib/Drupal/Core/Render/Element/Tableselect.php-
./lib/Drupal/Core/Render/Element/Textarea.php-
./lib/Drupal/Core/Render/Element/Textfield.php-
./lib/Drupal/Core/Render/Element/Token.php-
./modules/file/src/Element/ManagedFile.php-
29 have custom #process callback:
$ grep -r -A 1000 "\[FormElement(" . | grep "'#process'" | sort | grep -v test | awk '{print $1}'
./lib/Drupal/Core/Datetime/Element/Datelist.php-
./lib/Drupal/Core/Datetime/Element/Datetime.php-
./lib/Drupal/Core/Entity/Element/EntityAutocomplete.php-
./lib/Drupal/Core/Render/Element/Button.php-
./lib/Drupal/Core/Render/Element/Checkboxes.php-
./lib/Drupal/Core/Render/Element/Checkbox.php-
./lib/Drupal/Core/Render/Element/Color.php-
./lib/Drupal/Core/Render/Element/Date.php-
./lib/Drupal/Core/Render/Element/Email.php-
./lib/Drupal/Core/Render/Element/File.php-
./lib/Drupal/Core/Render/Element/Hidden.php-
./lib/Drupal/Core/Render/Element/MachineName.php-
./lib/Drupal/Core/Render/Element/Number.php-
./lib/Drupal/Core/Render/Element/PasswordConfirm.php-
./lib/Drupal/Core/Render/Element/Password.php-
./lib/Drupal/Core/Render/Element/Radio.php-
./lib/Drupal/Core/Render/Element/Radios.php-
./lib/Drupal/Core/Render/Element/Search.php-
./lib/Drupal/Core/Render/Element/Select.php-
./lib/Drupal/Core/Render/Element/Table.php-
./lib/Drupal/Core/Render/Element/Tableselect.php-
./lib/Drupal/Core/Render/Element/Tel.php-
./lib/Drupal/Core/Render/Element/Textarea.php-
./lib/Drupal/Core/Render/Element/Textfield.php-
./lib/Drupal/Core/Render/Element/Url.php-
./lib/Drupal/Core/Render/Element/VerticalTabs.php-
./lib/Drupal/Core/Render/Element/Weight.php-
./modules/file/src/Element/ManagedFile.php-
./modules/language/src/Element/LanguageConfiguration.php-
29 have custom #pre_render callback:
$ grep -r -A 1000 "\[FormElement(" . | grep "'#pre_render" | sort | grep -v test | awk '{print $1}'
./lib/Drupal/Core/Datetime/Element/Datetime.php-
./lib/Drupal/Core/Render/Element/Button.php-
./lib/Drupal/Core/Render/Element/Checkboxes.php-
./lib/Drupal/Core/Render/Element/Checkbox.php-
./lib/Drupal/Core/Render/Element/Color.php-
./lib/Drupal/Core/Render/Element/Date.php-
./lib/Drupal/Core/Render/Element/Email.php-
./lib/Drupal/Core/Render/Element/File.php-
./lib/Drupal/Core/Render/Element/Hidden.php-
./lib/Drupal/Core/Render/Element/MachineName.php-
./lib/Drupal/Core/Render/Element/Number.php-
./lib/Drupal/Core/Render/Element/Password.php-
./lib/Drupal/Core/Render/Element/Radio.php-
./lib/Drupal/Core/Render/Element/Radios.php-
./lib/Drupal/Core/Render/Element/Range.php-
./lib/Drupal/Core/Render/Element/Search.php-
./lib/Drupal/Core/Render/Element/Select.php-
./lib/Drupal/Core/Render/Element/Table.php-
./lib/Drupal/Core/Render/Element/Tableselect.php-
./lib/Drupal/Core/Render/Element/Tel.php-
./lib/Drupal/Core/Render/Element/Textarea.php-
./lib/Drupal/Core/Render/Element/Textfield.php-
./lib/Drupal/Core/Render/Element/Token.php-
./lib/Drupal/Core/Render/Element/Url.php-
./lib/Drupal/Core/Render/Element/VerticalTabs.php-
./modules/file/src/Element/ManagedFile.php-
9 have #element_validate callbacks:
$ grep -r -A 1000 "\[FormElement(" . | grep "'#element_validate' => " | sort | grep -v test | awk '{print $1}'
./lib/Drupal/Core/Datetime/Element/Datelist.php-
./lib/Drupal/Core/Datetime/Element/Datetime.php-
./lib/Drupal/Core/Render/Element/Color.php-
./lib/Drupal/Core/Render/Element/Email.php-
./lib/Drupal/Core/Render/Element/MachineName.php-
./lib/Drupal/Core/Render/Element/Number.php-
./lib/Drupal/Core/Render/Element/Table.php-
./lib/Drupal/Core/Render/Element/Url.php-
./modules/file/src/Element/ManagedFile.php-
or
to post comments
Comment
#7
pdureau
commented
2 April 2025 at 15:12
Current state of our investigation.
Slot or prop:
#description: (string) Help or description text for the element. In an ideal user interface, the #title should be enough to describe the element, so most elements should not have a description; if you do need one, make sure it is translated. If it is not already wrapped in a safe markup object, it will be filtered for XSS safety.
#description_display: (string) Where and how to display the #description.
#prefix: (string) Prefix to display before the HTML input element. Should be translated, normally. If it is not already wrapped in a safe markup object, will be filtered for XSS safety.
#suffix: (string) Suffix to display after the HTML input element. Should be translated, normally. If it is not already wrapped in a safe markup object, will be filtered for XSS safety.
#title: (string) Title of the form element. Should be translated.
#title_display: (string) Where and how to display the #title.
Already covered ion our POC:
#name
#default_value: Default value for the element. See also #value.
#required: (bool) Whether or not input is required on the element.
#value: Used to set values that cannot be edited by the user
To evaluate:
#ajax: (array) Array of elements to specify Ajax behavior. See the Javascript API and AJAX Forms guides for more information.
#disabled: (bool) If TRUE, the element is shown but does not accept user input.
#required_error: (string) Override default error message "@field_title is required" will be used if this is undefined.
or
to post comments
Comment
#8
grimreaper
French
France π«π·
commented
18 April 2025 at 07:47
Issue tags:
ddd2025
or
to post comments
Comment
#9
18 April 2025 at 07:48
grimreaper
opened
merge request !11876
or
to post comments
Comment
#10
grimreaper
French
France π«π·
commented
18 April 2025 at 07:49
Status:
Active
Β» Needs work
MR created from
, so now in this other issue MR, I can remove what is purely to have form element as SDC component.
or
to post comments
Comment
#11
pdureau
commented
19 April 2025 at 08:33
For information, Grimreaper is currently testing his proposal:
With a "normal" form fully built in a PHP class
As Field Widgets, using an
UI Patterns
mechanism (context-sensitive data sources, to retrieve and check field properties), but it will be relevant for other SDC usage
or
to post comments
Comment
#12
grimreaper
French
France π«π·
commented
20 April 2025 at 16:36
Status
File
Size
new
Peek 20-04-2025 18-33.gif
4.03 MB
Before cleaning my workspace, here is the result of friday at the end of DDD 2025.
or
to post comments
Comment
#13
3 June 2025 at 10:39
d34dman
made their first commit to this issueβs fork.
or
to post comments
Comment
#14
d34dman
commented
3 June 2025 at 10:42
Status:
Needs work
Β» Needs review
The latest changes adds some test coverage for usage of SDC Component as Form element.
or
to post comments
Comment
#15
grimreaper
French
France π«π·
commented
3 June 2025 at 11:14
Status:
Needs review
Β» Needs work
Thanks for this first round of tests!
or
to post comments
Comment
#16
d34dman
commented
3 June 2025 at 15:09
Related issues:
#3515506: Process #attributes render property for SDCs
or
to post comments
Comment
#17
pdureau
commented
3 June 2025 at 15:37
The other ones are internal, or not self-contained, or needs PHP. That means form elements created from SDC doesn't have:
#after_build: (array) Array of callables or function names, which are called after the element is built. Arguments: $element, $form_state.
#element_validate: (array) Array of callables or function names, which are called to validate the input. Arguments: $element, $form_state, $form.
#process: (array) Array of callables or function names, which are called during form building. Arguments: $element, $form_state, $form.
#value_callback: (callable) Callable or function name, which is called to transform the raw user input to the element's value. Arguments: $element, $input, $form_state.
...
After a talk with @d34dman we still believe those "processing" mechanism don't belong to the SDC, but an usage of the SDC by an applicative logic (so, most a time, as the return value of a plugin) must be able to add those form properties, which will be executed as they are normally are in a form element.
Example:
WidgetInterface::formElement(...) {
// Some applicative logic
return [
"#type" => 'component',
'#component' => 'foo:date',
'#slots' => [...],
'#props' => [...],
'#element_validate' => [...callback...]
];
or
to post comments
Comment
#18
pdureau
commented
3 June 2025 at 15:37
or
to post comments
Comment
#19
d34dman
commented
3 June 2025 at 15:39
@pdureau, thanks for summarizing it. I will add some test coverage for the support for "#element_validate". Keeping this as needs_work.
or
to post comments
Comment
#20
d34dman
commented
6 June 2025 at 08:53
Status:
Needs work
Β» Needs review
or
to post comments
Comment
#21
d34dman
commented
6 June 2025 at 14:26
Related issues:
#3404409: [Plan] Gradually replace Drupal's AJAX system with HTMX
Maybe we can think of adopting #htmx instead of #ajax?
or
to post comments
Comment
#22
nod_
French
Lille
commented
6 June 2025 at 15:11
That could be a good idea to only have the "modern" stuff, that would definitely help with BC headaches later on
or
to post comments
Comment
#23
pdureau
commented
23 July 2025 at 21:25
No update here but this issue is still working on π We are still excited and we are still targeting Drupal 11.3
or
to post comments
Comment
#24
d34dman
commented
23 July 2025 at 21:43
@pdureau,
Am not up-to-date regarding htmx in Drupal. Is there some plan which we can rely on for defining how ajax support can be brought about?
or
to post comments
Comment
#25
pdureau
commented
24 July 2025 at 07:12
@d34dman A lot of exciting work here:
#3404409: [Plan] Gradually replace Drupal's AJAX system with HTMX
or
to post comments
Comment
#26
d34dman
commented
24 July 2025 at 08:50
Please don't consider this as a criticism for the work being done by htmx team.
I find the way htmx is getting into Drupal quite counter productive for use case of SDC. If we look at HTMX spec, the idea is to be able to use it in a declarative way in html. So those dynamic instructions could be incorporated into Component itself.
Case in point, consider the example from
This translates in my mind, the implementation basically becomes
// Declarative implementation in Component
// Add supporting callback in a PHP controller
An equivalent in Drupal Ajax (status quo) would be (avoiding actual implementation for clarity),
// Inside Form Class use appropriate FormElement
// Inside Form Class add some declaration via "#ajax" key
// Supported in hook_form_alter
// Template (*.html.twig) prints "attributes"
And hence my dilemma in figuring out how this would translate.
This is an architecture problem where, how do we maintain the separation of business logic away from SDC component and support htmx.
We don't know yet, if htmx is going to be drop in replacement of #ajax in Drupal. If that be the case, we can implement ajax support as it is now, and swap it with htmx based on a promise that htmx would be drop in replacement of ajax.
or
to post comments
Comment
#27
nod_
French
Lille
commented
24 July 2025 at 09:07
It is not going to be a drop in replacement see
#3528440: Proof of concept, Replace ajax frontend implementation with htmx
. Trying to be backwards compatible is not reasonable. We tried that a few years ago with jQuery UI and autocomplete. Today autocomplete is still jQuery UI.
Going with htmx does mean we loose some separation of concerns for locality of behavior:
Where we draw the line is necessarily going to change.
or
to post comments
Comment
#28
d34dman
commented
24 July 2025 at 09:19
@nod_
It is not going to be a drop in replacement see
I stand corrected. Thats fresh news to me.
Does this mean, we can keep the ajax support for form elements when using "SDC" out of scope of this issue? We can create a follow up issue to track/guide users on how
ajax
htmx can be implemented in SDC component in a general way. My reasoning being, it (implementation of htmx, aka dynamic behaviour) need not be limited to Form Element at all.
or
to post comments
Comment
#29
needs-review-queue-bot
commented
19 September 2025 at 13:14
Status:
Needs review
Β» Needs work
Status
File
Size
new
3508641-nr-bot_g13hb1kn.txt
91 bytes
The
Needs Review Queue Bot
tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the
Drupal Contributor Guide
to find step-by-step guides for working with issues.
or
to post comments
Comment
#30
d34dman
commented
19 September 2025 at 15:04
@nod_, since this MR is in
#3535173: Support dynamic forms using HTMX
is merged, couldn't we use that for ajax and state support in this issue?
or
to post comments
Comment
#31
nod_
French
Lille
commented
19 September 2025 at 15:29
+1, yes
or
to post comments
Comment
#32
d34dman
commented
25 September 2025 at 21:24
@pdureau and @_node,
Thanks for the patience with me. Would it be possible to reduce the scope of this issue with explicit list of Elements and properties? Basically am trying to define the definition of done for this Issue so that I can complete the test and implementation in the upcoming MR
Form Elements in scope for target 11.3
I propose we remove "Actions" from scope as it somehow belong to category "container" like "fieldset" does. I feel we might want to approach this as a generic subject of use of containers/wrappers.
Textfield
Checkboxes
Checkbox
Submit button
Form Properties in Scope for target 11.3
we start with a list that we know for sure we want.
#title
#default_value
#disabled
#required
#description
Please let me know if you want to add more to this. Idea of keeping it simple means, we don't need to worry about backwards compatiblity. Example should we continue using "#value" or should we take the opportunity to refactor and bring "#read_only" or "#hidden" or something that make sense in SDC world, where it doesn't know about Drupal's internal.
or
to post comments
Comment
#33
25 September 2025 at 21:24
Version:
11.x-dev
Β» main
Drupal core is now using the
main
branch as the primary development branch. New developments and disruptive changes should now be targeted to the
main
branch.
Read more in the announcement
or
to post comments
Comment
#34
mglaman
WI, USA
commented
26 March 2026 at 20:54
Issue tags:
Chicago2026
I gave this a review and I think there's just a little tidying up that can be done
or
to post comments
Comment
#35
d34dman
commented
2 April 2026 at 15:58
Status:
Needs work
Β» Needs review
or
to post comments
Comment
#36
d34dman
commented
11 April 2026 at 15:07
The recent changes were basically stripping down the MR.
or
to post comments
Comment
#37
needs-review-queue-bot
commented
13 April 2026 at 16:43
Status:
Needs review
Β» Needs work
Status
File
Size
new
3508641-nr-bot_cvdym73b.txt
2.39 KB
The
Needs Review Queue Bot
tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the
Drupal Contributor Guide
to find step-by-step guides for working with issues.
or
to post comments
Comment
#38
grimreaper
French
France π«π·
commented
15 April 2026 at 18:15
In comment 7:
To evaluate:
#ajax: (array) Array of elements to specify Ajax behavior. See the Javascript API and AJAX Forms guides for more information.
#disabled: (bool) If TRUE, the element is shown but does not accept user input.
#required_error: (string) Override default error message "@field_title is required" will be used if this is undefined.
So #ajax is out of scope.
As #disabled and #required_error been tested? Does it need a special treatment?
or
to post comments
Comment
#39
d34dman
commented
15 April 2026 at 18:42
Update:
In the tests, I have removed assertions around processed state. Since we don't do ajax, and we don't prohibit processing of element, i guess thats should be ok.
or
to post comments
Comment
#40
grimreaper
French
France π«π·
commented
17 April 2026 at 10:47
Assigned:
grimreaper
Β» Unassigned
Status:
Needs work
Β» Needs review
Hello,
As pipeline is green, back to Needs review.
Discussed with @pdureau about #disabled, #required_error, let's include that later in follow up issue if needed.
or
to post comments
Comment
#41
mglaman
WI, USA
commented
20 April 2026 at 19:55
Status:
Needs review
Β» Reviewed & tested by the community
I can't approve the MR, but all of my concerns were addressed and we have a great start. core/lib/Drupal/Core/Render/Element/ComponentElement.php contains minimal changes to form element support.
RTBC will probably be rejected due to missing CR
or
to post comments
Comment
#42
d34dman
commented
20 April 2026 at 21:05
Am creating the CR
or
to post comments
Comment
#43
d34dman
commented
20 April 2026 at 21:47
Here is the link to Change Record
#3585881: SDC components can now be used as form elements
or
to post comments
Comment
#44
grimreaper
French
France π«π·
commented
21 April 2026 at 08:59
Thanks @d34dman for the CR :)
or
to post comments
Comment
#45
catch
he/him
commented
21 April 2026 at 11:23
This looks good to me, about 1:3 code to test ratio etc.
Because some scope has been cut here, do we need follow-ups to implement those things opened?
or
to post comments
Comment
#46
pdureau
commented
21 April 2026 at 11:39
Assigned:
Unassigned
pdureau
I will commit it.
Because some scope has been cut here, do we need follow-ups to implement those things opened?
The
#ajax
#htmx
debate? I am meeting Florent (@grimreaper) and Shibin (d34dman) at DDD Athens, we will plan the eventual follow-ups.
or
to post comments
Comment
#47
pdureau
commented
22 April 2026 at 07:37
Category:
Task
Β» Feature request
or
to post comments
Comment
#48
22 April 2026 at 08:11
pdureau
committed
671d81e9
on
main
feat: #3508641 Define form elements from SDC
By: pdureau
By: grimreaper...
or
to post comments
Comment
#49
pdureau
commented
22 April 2026 at 08:14
Assigned:
pdureau
Β» Unassigned
Status:
Reviewed & tested by the community
Β» Fixed
Committed
671d81e
and pushed to main. Thanks!
Do we backport to 11.x?
or
to post comments
Comment
#50
22 April 2026 at 08:14
Now that this issue is closed,
review the
contribution record
As a contributor, attribute any organization that helped you, or if you volunteered your own time.
Maintainers, credit people who helped resolve this issue.
or
to post comments
Comment
#51
smustgrave
commented
22 April 2026 at 16:11
Could we please backport to 11.4 :)
or
to post comments
Comment
#52
godotislate
he/him
commented
22 April 2026 at 16:20
Version:
main
Β» 11.x-dev
Status:
Fixed
Β» Patch (to be ported)
Not seeing anything 12.x specific, so we should try for 11.x backport.
or
to post comments
Comment
#53
grimreaper
French
France π«π·
commented
23 April 2026 at 08:25
Issue tags:
DevDaysAthens2026
or
to post comments
Comment
#54
mherchel
he/him
Gainesville, FL, US
commented
24 April 2026 at 12:37
Congrats everyone! Should
the change record
be published?
or
to post comments
Contribution record
Change records for this issue
SDC components can now be used as form elements
Child issues
#3519219: UI Suite Bootstrap: Define form elements from SDC
Add child issue
clone issue
Related issues
Referenced by
#501366: Add field grouping to Drupal core
#3486353: [5.0.x-beta1] Improve form management
#3494634: Compatibility between SDC and the Form API
#3519934: [2.2.0] Add ui_patterns_field_widgets sub-module
#3533198: [Meta] Make Drupal the first "design-system native" CMS + Unify & simplify render & theme systems
Infrastructure management for Drupal.org provided by
Need a Drupal 7 extended support partner? Consider Tag1.
News items
News
Planet Drupal
Social media
Sign up for Drupal news
Security advisories
Jobs
Our community
Community
Services
Training
Hosting
Contributor guide
Groups & meetups
DrupalCon
Code of conduct
Documentation
Documentation
Drupal Guide
Drupal User Guide
Developer docs
API.Drupal.org
Drupal code base
Download & Extend
Drupal core
Modules
Themes
Distributions
Governance of community
About
Web accessibility
Drupal Association
About Drupal.org
Drupal is a
registered trademark
of
Dries Buytaert