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 https://getbootstrap.com/docs/5.3/forms/checks-radios/#switches
Let's deal with the second point in this dedicated ticket.
Proposed resolution
https://www.drupal.org/docs/drupal-apis/form-api/form-render-elements
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 - ...
- inline twig transformation for
- 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 |
|---|---|---|---|
| #56 | 3508641-56-sdc-form-element-validation-problems.png | 86.89 KB | kentr |
| #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.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3508641-define-form-elements
changes, plain diff MR !11876
Comments
Comment #2
grimreaperComment #3
grimreaperComment #4
grimreaperComment #5
grimreaperProposed 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".
Comment #6
pdureau commentedLet's have a look on all 37 Render Elements from Core:
The "inside" of form elements
23 of them are direct calls to theme hooks
file_managed_file:
input.html.twig:
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:
5 are using
#theme_wrappersinsteadcheckboxes.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:
How are they built? Do they have templates?
Wrappers
2 have custom wrapper
datetime-wrapper.html.twig:
19 are using standard form_element wrapper
form-element.html.twig:
What about the 16 (17?) others?
Processing
20 have a custom valueCallback()
29 have custom #process callback:
29 have custom #pre_render callback:
9 have #element_validate callbacks:
Comment #7
pdureau commentedCurrent state of our investigation.
Slot or prop:
Already covered ion our POC:
To evaluate:
Comment #8
grimreaperComment #10
grimreaperMR created from https://git.drupalcode.org/project/drupal/-/merge_requests/11866, so now in this other issue MR, I can remove what is purely to have form element as SDC component.
Comment #11
pdureau commentedFor information, Grimreaper is currently testing his proposal:
Comment #12
grimreaperBefore cleaning my workspace, here is the result of friday at the end of DDD 2025.
Comment #14
d34dman commentedThe latest changes adds some test coverage for usage of SDC Component as Form element.
Comment #15
grimreaperThanks for this first round of tests!
Comment #16
d34dman commentedComment #17
pdureau commentedAfter 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:
Comment #18
pdureau commentedComment #19
d34dman commented@pdureau, thanks for summarizing it. I will add some test coverage for the support for "#element_validate". Keeping this as needs_work.
Comment #20
d34dman commentedComment #21
d34dman commentedMaybe we can think of adopting #htmx instead of #ajax?
Comment #22
nod_That could be a good idea to only have the "modern" stuff, that would definitely help with BC headaches later on
Comment #23
pdureau commentedNo update here but this issue is still working on 👍 We are still excited and we are still targeting Drupal 11.3
Comment #24
d34dman commented@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?
Comment #25
pdureau commented@d34dman A lot of exciting work here: #3404409: [Plan] Gradually replace Drupal's AJAX system with HTMX
Comment #26
d34dman commentedPlease 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 https://htmx.org/docs/
This translates in my mind, the implementation basically becomes
An equivalent in Drupal Ajax (status quo) would be (avoiding actual implementation for clarity),
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.
Comment #27
nod_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: https://htmx.org/essays/locality-of-behaviour/ Where we draw the line is necessarily going to change.
Comment #28
d34dman commented@nod_
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
ajaxhtmx 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.Comment #29
needs-review-queue-bot commentedThe 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.
Comment #30
d34dman commented@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?
Comment #31
nod_+1, yes
Comment #32
d34dman commented@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.
Form Properties in Scope for target 11.3
we start with a list that we know for sure we want.
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.
Comment #34
mglamanI gave this a review and I think there's just a little tidying up that can be done
Comment #35
d34dman commentedComment #36
d34dman commentedThe recent changes were basically stripping down the MR.
Comment #37
needs-review-queue-bot commentedThe 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.
Comment #38
grimreaperIn comment 7:
So #ajax is out of scope.
As #disabled and #required_error been tested? Does it need a special treatment?
Comment #39
d34dman commentedUpdate:
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.
Comment #40
grimreaperHello,
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.
Comment #41
mglamanI 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
Comment #42
d34dman commentedAm creating the CR
Comment #43
d34dman commentedHere is the link to Change Record #3585881: SDC components can now be used as form elements
Comment #44
grimreaperThanks @d34dman for the CR :)
Comment #45
catchThis 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?
Comment #46
pdureau commentedI will commit it.
The
#ajax/#htmxdebate? I am meeting Florent (@grimreaper) and Shibin (d34dman) at DDD Athens, we will plan the eventual follow-ups.Comment #47
pdureau commentedComment #49
pdureau commentedCommitted 671d81e and pushed to main. Thanks!
Do we backport to 11.x?
Comment #51
smustgrave commentedCould we please backport to 11.4 :)
Comment #52
godotislateNot seeing anything 12.x specific, so we should try for 11.x backport.
Comment #53
grimreaperComment #54
mherchelCongrats everyone! Should the change record be published?
Comment #55
pdureau commentedI will port to 11.x
Comment #56
kentr commentedI didn't see
#titlein the MR or the CR. Did it get implemented?Asking because my understanding is without
#titleon the element, validation error messages will be empty.I played around with some examples on a real form, and that is what I observed.
Might only apply on 11.4+ with HTML5 form validation disabled, or with the Disable HTML5 validation module.
Here's a screenshot. Both fields are required, and the form was submitted with both fields empty. There should be error messages for both fields, but there's only one error message.
Also noticed that the
inputdoesn't have theerrorclass or thearia-invalidattribute.Here's the gist of the element definition, just an element from
FormTestValidateRequiredFormwith'#required' => TRUEadded.I'm happy to be told that I'm doing something wrong. If I'm not, this shouldn't go into a release until those are worked out. They're big UX and accessibility issues.
Adding
static::setAttributes($element);toComponentElement::mergeElementAttributesToPropAttributes()gets part of the way to having theerrorclass or thearia-invalidattribute.Comment #58
pdureau commentedThanks Kent for your interest about this contribution. Me and Florent (@grimreaper) have studied your feedback.
Form properties
There are many form properties we didn't automatically manage yet, because the merged commit only support:
#name(string)#required(bool)#value#titlemay be a good candidate for a future addition inform_state"magic" prop, it seems our model is extensible enough to not block this. Can you create a follow-up issue? We will be happy to discuss there.Attributes
We are already injecting
#attributesto the components and we expect component authors to use it as "wrapper attributes" or "item attributes". We know it is a but light and we can go further in follow-up issues.However, let's be careful about not being too much normative here, the UI components own their markup, which is most of the time defined upstream, sometimes in a design system. So, instead of sending Drupal markup, it would be better to send some additional information about the form state.
For example, the Drupal
.errorclass may not be the expected class in the component, which may be using something like.is-invalid(in Bootstrap) or.input-error(in Daisy UI), so it may be better to injectform_state.value.invalidboolean value.In our opinion, we must be normative for properties filling those requirements:
And be loose for everything else.