Problem/Motivation
datetime-wrapper.html.twig element label uses an <h4> tag instead of the <label> tag.
This is problematic for assistive tech, since the "label" is not associated with anything.
For example, any node form in core has an "Authored on" datetime form element. The individual date and time inputs already have <label for>. For assistive tech, these are programmatically labelled as "date" and "time". However there's no programmatically-determinable association between these inputs and the phrase "authored on".
This <h4> also helps breaks #states support for datetime elements, but that's being addressed in #2419131: [PP-1] #states attribute does not work on #type datetime, not here.
Proposed resolution
Option A: Nested fieldsets:
Option A is now the agreed upon solution, with sign-off from @andrewmacpherson and @lauriii.
The "authored on" group would be improved by treating it as a fieldset with at legend, instead of a <h4>.
In the case of a single datetime field (Field API, datetime module) we already get a fieldset, using the the field name as a legend. The individual date and time inputs have <label for>, so screen reader users skipping through form elements will hear "preferred date, date" (for example). That's great for a single-value datetime field.
In the case of a datetime range field (datetime_range module), the field name is treated as a legend, but the start and end each have a h4 from the datetime-wrapper. This might be better with nested fieldsets (field_name (legend) > start date (legend) > date (label). Nested fieldsets are sometimes confusing for screen reader users though, so we'd want to tread carefully here.
Nested fieldsets would also be ugly and potentially confusing once #2625136: Fix label visibility and add wrapper container for exposed numeric/date filters with multiple form elements lands, too.
Option B: WAI-ARIA labeledby + group
Nice thought, but rejected. See #27 for reference.
Remaining tasks
Discuss and agree on an approach.Option A it is.- Fix implementation to not generate duplicate IDs and other problems with patch #35
- Figure out how to properly deprecate the datetime-wrapper template + pipeline: #3157353: Provide a mechanism to mark entire twig templates as deprecated
- Update / add tests as needed.
- Fix any accessibility concerns with nested fieldsets.
- Fix obvious visual stuff we can do to make this prettier by default.
- Reviews:
- General implementation / code review
- Needs accessibility review
- Needs usability review
- Write change record about the render array changes, deprecation of the datetime-wrapper template, etc
- RTBC.
- Commit.
- Unblock #2419131: [PP-1] #states attribute does not work on #type datetime (which is thankfully mostly solved by this approach, but will need some follow-up fixes)
User interface changes
Claro before:

Olivero before:

Claro after:

Olivero after:

API changes
None.
Data model changes
None.
Release notes snippet
TBD.
Original report by @tormi
datetime-wrapper.html.twig element label uses <h4> tag instead of default <label> tag.
| Comment | File | Size | Author |
|---|---|---|---|
| #93 | 3078334-claro-after.png | 213.08 KB | sokru |
| #93 | 3078334-olivero-after.png | 180.75 KB | sokru |
| #93 | 3078334-olivero-before.png | 123.31 KB | sokru |
| #93 | 3078334-claro-before.png | 168.89 KB | sokru |
| #75 | content-entity_datetime.png | 6.75 KB | tyler36 |
Issue fork drupal-3078334
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:
Comments
Comment #2
tormiComment #3
tormiPatch added.
Comment #4
tormiComment #5
tormiComment #6
tormiComment #7
tormiComment #8
andrewmacpherson commentedThanks for filing this @tormi.
The way we use H4 here is a bit awkward, sure. We don't use headings for fields or form elements in general. It makes these form elements seem more important than other ones.
I think it's worth revisiting how we label our compound date and time inputs, to see if we can improve semantics in a reliable way.
However, just replacing the
<h4>with<label>here isn't ideal. It results in a label which isn't programmatically linked to any input. It may as well be aspan.In the case of the node "authored on" info, the individual date and time inputs already have
<label for>. For assistive tech, these are programatically labelled as "date" and "time". However there's no programmatically-determinable association between these inputs and the phrase "authored on" (either before or after the patch here). The "authored on" group would be improved by treating it as a fieldset with at legend, instead of a h4.In the case of a single datetime field (Field API, datetime module) we already get a fieldset, using the the field name as a
legend. The individual date and time inputs have<label for>, so screen reader users skipping through form elements will hear "preferred date, date" (for example). That's great for a single-value datetime field.In the case of a datetime range field (datetime_range module), the field name is treated as a
legend, but the start and end each have ah4from the datetime-wrapper. This might be better with nested fieldsets (field_name (legend) > start date (legend) > date (label). Nested fieldsets are sometimes confusing for screen reader users though, so we'd want to tread carefully here.Review of patch #3:
This only changes the template in the stable theme. so it has no effect when using Seven. Whatever changes we make here should be repeated for each of the
datetime-wrapper.html.twigtemplates: in system module, stable theme, and classy theme.Comment #10
tormiComment #11
michaellenahan commentedWorking on this at Drupalcon Amsterdam
Comment #12
waliur commentedWorking on this at Drupalcon Amsterdam
Comment #13
joshk commentedHelping out with this today!
Comment #14
joshk commentedComment #15
joshk commentedTo reproduce the issue, you can use a vanilla installation:
Visit
/node/add/articleAnd take a look in the "authoring information" section and you'll see the incorrect behavior.
Comment #16
waliur commentedComment #17
waliur commentedHow to find the 4 files
PHP Storm: Navigate --> File --> Search for "datetime-wrapper.html.twig"
* core/modules/system/templates/datetime-wrapper.html.twig
* core/themes/claro/templates/datetime-wrapper.html.twig
* core/themes/classy/templates/form/datetime-wrapper.html.twig
* core/themes/stable/templates/form/datetime-wrapper.html.twig
*Banana skin warning*
Seven admin theme inherits from Classy theme. So by editing the file in core/themes/classy/templates/form/datetime-wrapper.html.twig it should you should be able to replicate and fix the issue.
Before
After
Comment #18
waliur commentedI've edited the 4 files. See attached patch.
Comment #19
waliur commentedComment #20
tormi@andrewmacpherson, we've basically replicated the initial solution to other themes as well.
Comment #21
john cook commentedThanks to @Waliur, @joshk, and @michaellenahan for working on this.
I've tested this and the the results match the screen shots by @Waliur in comment #17.
As this touches the stable theme, this will need to have the "Needs frontend framework manager review" tag when it's ready for RTCB.
@andrewmacpherson in comment #8 states that "just replacing the
<h4> with <label> here isn't ideal" and "this might be better with nested fieldsets". The current patch doesn't wrap the fields in a fieldset. Setting back to "Needs work" because of this.Comment #22
dwwYes, this is broken. However, we already need to fix this as part of #2419131: [PP-1] #states attribute does not work on #type datetime which is a much older issue, with a bunch more people following it, and there's already working patches and tests for this change. There's also word in there from @lauriii (Front end framework manager) that we're not allowed to fix this bug in Classy for BC reasons...
Comment #23
andrewmacpherson commentedAh, lots of new comments. Thanks for working on this at DrupalCon Amsterdam. Tagging for another look at this once the 8.8.0 beta/RC rush has died down.
Meanwhile, @dww told me about another issue with some overlap with this one. #2419131: [PP-1] #states attribute does not work on #type datetime. We should look at these carefully together, and maybe merge them.
Comment #24
dwwUpon further discussion in Slack, @andrewmacpherson and I agreed to re-open this and postpone #2419131: [PP-1] #states attribute does not work on #type datetime on getting this fixed, first.
Initial stab at a real issue summary.
Comment #25
andrewmacpherson commentedThanks @dww!
Comment #26
dwwRe: fieldsets:
A fieldset + legend would also be problematic when using a datetime element in an exposed filter on a view. That's (part of) why I'm trying to fix this bug, to unblock #2648950: [PP-2] Use form element of type date instead textfield when selecting a date in an exposed filter (one of the top-20 most followed issues in the core queue).
Especially in combo with #2625136: Fix label visibility and add wrapper container for exposed numeric/date filters with multiple form elements -- we'd have nested, ugly (and potentially confusing) fieldsets/wrappers/etc. Ooof. :/
More updates to the summary, including pasting in large chunks of #8 into the proposed resolution section.
Comment #27
dwwInstead of nested fieldsets, is this a possibility?
Approach 2: Associating related controls with WAI-ARIA:
It suggests code that looks like this:
Adopting the markup from Bartik's
Authored onand adjusting for the above, I get something like this:Could that save us the visual clutter of nested fieldsets, but still be semantically meaningful and fully accessible with assistive tech?
https://caniuse.com/#feat=wai-aria seems pretty solid across browsers that D8+ core is supporting, no?
A quick grep indicates we're already using
aria-labelledbyin some parts of core.Pardon my ignorance, but is this worth exploring? I put it into the summary as option B (and clarified/updated option A for fieldsets).
Thanks!
-Derek
Comment #28
dwwRemoving visual clutter from the example proposed markup in option B. ;) Oh the irony. Want to make it easier to make sense of what's being proposed. Leaving the full details in #27 if someone really wants to see everything.
Comment #29
dwwIn Slack, @andrewmacpherson told me:
https://www.w3.org/TR/using-aria/#firstrule
So, that's probably going to be a strong vote for option A - nested fieldsets.
Here's a very rough and still quite broken patch that starts down that road. This is somewhat based on the changes I wrote for #2419131: [PP-1] #states attribute does not work on #type datetime. Lots of @todo and incomplete, but it's a proof-of-concept on the approach. Do not queue this for testing, not even close to being ready. But enough to get initial screenshots on Seven.
Before
Clean 8.8.x dev site, with an 'Event' node type with 3 fields: date-only, datetime, and datetime_range, and 'Authored on' visible:
After
Patch applied, caches cleared, re-load the node/add/event form:
TODO
#theme => 'fieldset_legend'pipeline or something (akin to#theme => 'form_element_label').All that said, I'm still not sold this is better than ARIA group + described by. ;) This is turning into a much more disruptive change (both markup/code, and visually). Yes, I hear the First Rule and I understand it. But this might be a situation to re-consider. If we "only" added the wrapper div with group + described_by, we'd have a lot less to do here, we wouldn't introduce so much visual change, but we could still make all this much more accessible than it is now.
Comment #30
andrewmacpherson commented#27:
Not really. Options A and B are semantically equivalent as far as assistive tech is concerned; try inspecting that page with the Firefox accessibility tree inspector to see what the browser sends to the OS-level accessibility API.
What the WAI article doesn't make clear, is that option B is really just a fallback for where you can't use HTML fieldset/legend for some reason. See the First Rule of ARIA Use. Situations where you might use the ARIA faux-fieldset are:
There's little point in option B, because Drupal already has all the theming infrastructure for the HTML fieldset/legend (render element, theme_wrapper, preprocess, twig, and Classy CSS).
Fieldsets don't have to be visual clutter. It's fine to style a fieldset with
border: none;. The legend can be visually-hidden -template_preprocess_fieldset()already handles'#title_display' => 'invisible'for fieldset legends.I recall reading that there are some bugs using CSS grids to arrange content inside of fieldset elements, but we can probably avoid that for our date+time use case.
Comment #31
andrewmacpherson commented#29: I skim-read the patch to see how you did it.
It might be simpler. Can you re-use the existing code for fieldsets, with something like:
$element['#theme_wrappers'][] = 'fieldset';There's an example of this in
DateTimeDateTimeWidget, which uses a fieldset wrapper.There's also a similar example in
FileWidget, but that uses a details wrapper.I haven't completely grokked how
#theme_wrappersworks, and there aren't many instances of it used in core, but I looked around the preprocess function for the fieldset and it already handles invisible legends.Comment #32
dwwRe: #31 -- yeah, I noticed #theme_wrappers, too. I was going to give that a spin. As I said, #29 was a super quick/dirty PoC to start to see how this would behave as nested fieldsets.
I *think* we might be able to avoid the double fieldset on the datetime (not datetime_range) field widget by conditionally checking if something upstream already set a fieldset #theme_wrapper, and if so, leaving it alone instead of adding our own.
Alternatively, we might have to change the upstream field widget to not wrap this in a fieldset, which might have more BC implications.
Re: visual clutter -- yeah, we can do some CSS tricks to make this look better, but not in classy itself, only seven, claro, bartik. At least that's my understanding of our front end BC policies. I think we need to see the legend in all these cases, not just hear it, so I don't think
#title_display => 'invisible'is our friend here. But maybe some targeted CSS to NOT VISUALLY SHOUT it would be good (and acceptable to @lauriii et al). ;)Anyway, I'll see if I can find time to push this forward in the near future. I'd love to see this fixed before 8.8.0 (among other reasons, since it's blocking a lot of other issues I care about), but I worry it's not going to be a candidate after beta1 (which is happening basically now). I'm hopeful since it's an #accessibility bug that it'll be acceptable during beta.
Thanks,
-Derek
Comment #33
dww(better title to more accurately convey the bug we're trying to fix)
Comment #34
dwwStill needs work, but putting this up for review on the new approach.
Once we're inside
template_preprocess_datetime_wrapper(), it's too late for#theme_wrappersto do any good. We've already decided what template we're using at that point.So, here's an attempt at solving the problem at a different layer of the chain. Now, the underlying
Datetime(and I discovered,Datelist) elements completely avoid usingdatetime_wrapperat all. They take on the smarts about if they have multiple form elements, and if so, using afieldsetfor their#theme_wrappers, notdatetime_wrapper.This also means that the Field API widget plugins from Datetime module no longer have to worry about the conditional fieldset stuff -- that's handled directly by using
'#type' => 'datetime'or'#type' => 'datelist'render arrays.So here's the minimum patch that accomplishes all this. However, it really means that *all* of the datetime_wrapper plumbing is now dead code. We should probably rip it completely out. But let's do that as a separate patch. I don't fully understand the ramifications of this for BC, but I don't think they're pretty. ;) I suspect @Lauriii will say we can't go this far.
But it does seem pointless to take our existing datetime_wrapper template + preprocess world and have it duplicate the fieldset world, so I like the idea of ripping out custom (broken) handling for this and using existing functionality.
I don't know how we deprecate twig templates for removal. Maybe we need a
@trigger_error()insidetemplate_preprocess_datetime_wrapper()that says it's no longer supported, not invoked by core, and if you're calling it directly for some reason, you should stop?I also didn't start trying to do theme-specific CSS to make this any prettier.
Anyway, would love feedback on this approach before I/we go any further.
Before (now with a Datelist)
After
Thanks!
-Derek
p.s. Radically different patch, so no interdiff from #29.
Comment #35
dwwHere's how much code we can remove if we do this. ;)
Again, I'm guessing the BC considerations makes this un-doable. But it's a nice thought...
Curious what the bot thinks of this.
Comment #37
dwwThat's less failing than I feared. ;)
In related news, I took my new test module and JS test from #2419131-136: [PP-1] #states attribute does not work on #type datetime (but no other changes from that issue), applied it on top of #36, and #states basically All Just Works(tm). The only part that doesn't is the "required" myth, but a) that should be easy to fix and b) I believe it's completely stupid that #states lets you visually toggle that since it has no bearing on anything. ;) But whatever.
Comment #38
dwwJust had a Slack chat w/ @lauriii about this. Pasting his replies with permission:
So yay! In principle, we can proceed down the path of #35. Huzzah!
Updating the summary to clarify we're committed to option A (nested fieldsets), and to update remaining tasks accordingly.
Comment #40
smustgrave commented@dww would you say the patch in #35 is good to go for 8.9? Or would you have another recommendation? The problem I'm currently facing is for a government mandated content type where certain fields need to be made required if the value of a dropdown is A or B.
Comment #41
dww@smustgrave: No, #35 is still broken, as the test results show. :( Just haven't had time to move this forward again, and no one else has stepped up to help.
Note: Using
#statesto control required-ness is a total joke. All it does is toggle the little red asterix thingy. If you actually want the fields to be conditionally required, you need to implement custom validation. It's extra wonky if you're dealing with an entity form, since then there's entity constraints to deal with, too. It's a mess. If you have a budget and want to hire me for a few hours to consult you on it, let me know.Thanks/sorry,
-Derek
Comment #42
dwwWidening the scope of the title to match where we've ended up.
Comment #43
dwwOpened #3157353: Provide a mechanism to mark entire twig templates as deprecated to address point 3 in remaining tasks.
Comment #47
jeroentCreated a reroll.
Comment #48
jeroentComment #49
jeroentComment #51
jhedstromMoving to NW for failing tests and MR re-roll.
Comment #56
eduardo morales albertiCreated merge request for 9.4.x https://git.drupalcode.org/project/drupal/-/merge_requests/2730
Created merge request for 9.5.x https://git.drupalcode.org/project/drupal/-/merge_requests/2731
Comment #57
eduardo morales albertiTest needs revision:
Comment #58
smustgrave commentedAppears to cause a regression UX wise.
Also adding a new for a CR since a new template will be needed to removed by themes.
Comment #60
smustgrave commentedShould the fieldsets be getting an aria-describedby attribute? Reading the failed tests and on
Drupal 10.1.x standard install
I added a datetime field to the basic content type
I don't see the attribute.
Comment #62
smustgrave commentedTagging for accessibility review to make sure this is going in the right track.
Fixed up the tests a bit from the 9.5/9.4 MR and updated form.inc to add that aria-describedby
But under this scenario. If the date field storage is set to just date and not datetime. Should it be wrapped? Does it need aria-describedby? Commented that one assertion out to see what else fails.
Comment #63
smustgrave commentedThis issue is being worked on in the MR
Please do not reroll a patch
Only uploading this because the MR needs fixing.
Comment #65
rkollerI've applied the MR3221 to a Drupal 10.1.0-dev install with PHP 8.2.0 and quickly tested with voiceover on macos 12.6.1 two or three days ago - but haven't had the time and capacity to write up a comment until now. when i've updated the dependencies to the install today, before writing up the comment, the patch failed to apply now (but not sure if there was another commit since my recording). I've noticed a few details in the linked videos. I went into an existing node created for an anonymous user with devel and wanted to change the date to 2023-05-12 and change the hour value to 19. i've tested in safari 16.1 and the latest version of firefox and edge:
Safari (16.1)
authored onisn't announced.The time that the node was created.is announced twice, once for the date field and once for the time field01/05/2023but in contrast voiceover announcesYYYY-MM-D. The actual value isn't announced and based on the announced format of YYYY-MM-D i've entered 2023 as the first value which ends up to be 03 then. i tab once and enter 05 for the month and the value ends up to be 05 (which is correct). i tab once again and enter 12 for the day and so the year is set to 0012 now.12htime format (am/pm) but voiceover only announceshh-mm-ss. as an european i am used to a24htime format (that is also what i am using in my operating system). with the announcement ofhh-mm-sssimply relying on the aural presentation i would expect it to be a 24h time format. if i enter19as the hour value the actual value is set to 12Firefox (Latest version)
authored onis correctly announced.Edge (Latest version)
authored onis correctly announced.Comment #66
smustgrave commentedSounds like got some work to do! Thanks for the detailed tests.
Also sounds like the tests can be updated as those aria described by fields may change
Comment #67
mgiffordComment #69
heddnCan we re-base the MR on 11.x?
Comment #70
smustgrave commentedDon't have time to actually rebase but updated target.
Comment #72
lauriiiI rebased the MR. It was not really possible at first because there were merge commits in the branch. Luckily it was pretty straight forward to get rid of the merged commits because there were no merge commits before the MR commits. I ran
git reset --hard a9d6facb4e, wherea9d6facb4ewas the last commit before the commits that were introduced by the merge commit. After that I could rungit rebase 11.xwithout any difficult conflicts to resolve.Thanks for the review @rkoller! I'm wondering if you've tested the experience before this patch? It would be helpful if we could figure out how the experience changes as the result of this issue, so that we know which issues needs to be tackles here, and which issues should be moved into their own bug reports.
It looks like I mentioned this already in #38, but putting it again here for visibility. Instead of just removing the
datetime_wrapperfromdrupal_common_theme(), we need to deprecate it. Unfortunately we haven't defined the steps for this #3157353: Provide a mechanism to mark entire twig templates as deprecated so this is something we'd need to come up with.Comment #74
gcbHere's the current state of the MR as a patch file for stable use in composer builds. This applies for me on 10.2.4.
Comment #75
tyler36 commentedAdding note on "current" behaviour for Drupal
10.2.5with Claro admin theme.I have a custom entity with 2 fields:
-
BaseFieldDefinition::create('timestamp')-
BaseFieldDefinition::create('timestamp')When rendering by
Drupal\Core\Entity\ContentEntityForm, they appear as such:However, changin the definition to
BaseFieldDefinition::create('datetime')and clearing the cache:Not sure if current MR/patch updates 'timestamp' too, but I would expect them to both display the same.
Comment #76
tyler36 commentedComment #77
sneezycheesy commentedI've updated the patch for Drupal 10.3.1
Comment #79
anish.a commentedI was trying to rebase with 11.x. Some functions are deprecated. Not sure which way should I go to fix this.
Comment #80
mgiffordComment #81
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 #85
acbramley commentedI've merged 11.x into this branch and resolved all conflicts. I've reverted some of the code that removed datetime_wrapper theme hooks and preprocessing and I think we need to add the templates back as well and deprecate that properly.
Comment #86
apmsooner commentedHas the datetime-local element been considered as replacement for some of these scenarios? If there's a time element, this option is a much better UX to eliminate wrappers. Theres a few related patches that fix a few datetime-local issues.
There are a few browser compatibility issues for datetime-local with safari & firefox not showing the time picker but time can still be entered. Is that a deal breaker?
Internet Explorer doesn't support it all. Do we still care about IE?
For just dates, a custom element that extends Datetime would work that just removes the theme wrappers and replaces the date title with the element title. The only one that should get a fieldset wrapper IMO is the date list widget if we could adopt datetime-local.
Comment #88
prudloff commentedI added the templates back and added a deprecation.
And I drafted a change record: https://www.drupal.org/node/3530128
Comment #89
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 #90
prudloff commentedI merged the latest 11.x and applied suggestions.
Comment #91
smustgrave commentedThink we need screenshots of what this going to look like after in claro and olivero. May need UX sign off depending on how severe the change is.
Comment #93
sokru commentedAdded screenshots, there's at least small padding issue caused by css rule
Comment #94
sokru commented