Problem
Messages currently have a role="contentinfo" wrapper for status messages (compared with role="alert" for error messages).
contentinfo is inappropriate here, because it's supposed to be for information about the document as a whole, not status updates. It's a landmark role, and there should only be one such contentinfo region per page. It's intended to serve the same purpose as a top-level HTML footer element. Using contentinfo for messages may be present some confusion for users who navigate by landmark regions.
Proposed resolution
- Replace the
role="contentinfo"from the message-type wrapper DIV withrole="region"and change thearia-labelto "System Messages". - Leave the
<div role="alert">in place for the error message group. - Add a
<div role="status">wrapper for Status and Warning message groups.
Here's a simplified before/after summary of this.
Structure BEFORE:
div role="contentinfo" aria-label="Status message"
h2.visually-hidden
ul
div role="contentinfo" aria-label="Error message"
div role="alert"
h2.visually-hidden
ul
Structure AFTER:
div role="region" aria-label="System messages"
div role="status" // Status & Warning messages
h2.visually-hidden
ul
div role="alert" // Error messages
h2.visually-hidden
ul
| Comment | File | Size | Author |
|---|---|---|---|
| #78 | 2942404-contentinfo-landmark.patch | 10.39 KB | bapplejax |
| #65 | 2942404-65-11.x.patch | 11.71 KB | bapplejax |
Issue fork drupal-2942404
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
andrewmacpherson commentedNote this issue might turn out to be a duplicate. I have a strong recollection that this has been discussed before, but I can't find an issue. I think it might be buried in a comment thread somewhere.
Not sure if we should require manual testing to demonstrate an actual improvement before committing this. Not all screen reader + browser combinations announce
alertandstatusregions on page load, and I don't think the ARIA recs actually require them to do so. Butstatusis the correct role semantically, so this fix is worth doing.I've read about approaches which try to force
alertandstatusannouncements on page load by using JS to tweak the element just after page load. I'm against trying to manage that ourselves, let's leave it up to conforming browsers and AT to decide whether to announce them. This may have an added benefit of respecting user preferences for any AT that provides them.Comment #3
mgiffordMakes sense to me:
https://w3c.github.io/html-aria/#index-aria-status
A container whose content is advisory information for the user but is not important enough to justify an alert, often but not necessarily presented as a status bar.
https://w3c.github.io/html-aria/#index-aria-contentinfo
A large perceivable region that contains information about the parent document.
Comment #4
andrewmacpherson commentedHere are some notes about the current markup. My proposal will be in a later comment...
A PHP snippet to generate some messages. I've been using this with Devel module's Execute PHP tool.
Here's a screenshot of the messages you get from that PHP snippet:
A summary of the HTML markup looks like this (output from
status-messages.html.twig, I've omitted classes from the Classy theme):Notes:
role="contentinfo"wrapper, with anaria-labelcontaining the message type.role="alert" wrapper", as well as therole="contentinfo"wrapper.The headings structure for the front page (in Bartik) looks like this. It shows one H2 per message type.
Comment #5
andrewmacpherson commentedProposal - update status-messages.html.twig,/code> in system module and themes.
The
role="contentinfo"is wrong. This role is for a landmark region, equivalent to HTML<footer>, with the extra proviso that there can only be onecontentinfolandmark per document, and it must be a top-level landmark. I'm not sure how we ended up withrole="contentinfo", but perhaps the idea was to make the messages easy to find using landmark regions. I like that idea, and we can use<div role="region">instead, and it will count as a landmark region, as long as it has an ARIA label.Proposed changes:
role="contentinfo"from the message-type wrapper DIV.role="region", so it remains in landmark-region navigation for assisitive tech.aria-label, which won't be needed if it doesn't have a role.<div role="alert">in place for the error message group.<div role="status">wrapper for Status and Warning message groups.Here's a before/after summary of this. I've left out classes from Classy.
Structure BEFORE:
Structure AFTER:
The only thing we are changing here is the value of some existing role attributes, and inserting an extra DIV wrapper for some groups (which is an implicit aria-live region, but otherwise not referred to by any existing CSS or JS). No classes or IDs are affected.
I think we could get away with this, and manage to avoid breaking any custom themes.
I don't think I've ever seen custom message templates on a web site's front-theme, though I expect some admin themes have done so.
Some other ideas:
role="region"for each message type, or a singlerole="region"for all messages, with 3 headings inside?Comment #7
rainbreaw commentedI just discovered this issue while looking at Claro — https://www.drupal.org/project/drupal/issues/3079738#comment-13249979 — which also uses contentinfo for status messages. It looks like this has been open for some time. Is there a reason that I'm not aware of that we are still using contentinfo when we should be using status?
Comment #8
andrewmacpherson commentedBecause no-one has worked on the issue, that's all :-(
Comment #9
damienmckennaComment #11
lus commentedI have updated the status message templates for the basic and test themes by changing the role of
contentinfotoregionand adding a wrapper with rolestatusaround the warning and status messages.# The tests are failing because of the role change I gues.
Comment #12
lus commentedWell I have tried to modify the tests cases, but it seems that there are still two erros, that I don't know how to solve.
Comment #14
sd9121 commentedComment #15
sd9121 commentedPlease review the patch.
Thanks!
Comment #17
sd9121 commentedComment #18
andrewmacpherson commented@sd9121 - what has changed in your version of the patch, compared with the previous one?
This is a good issue to review during the Accessibility Bug Bash during DrupalCon Global 2020. Might need an issue summary update, following #5, to keep the proposed resolution up to date.
The later patches here don't have interdiffs, so it's worth checking what changed from one version to the next.
Comment #19
mgiffordWith
aria-label="Status message"&<h2 class="visually-hidden">Status message</h2>being used, won't the "Status message" message be said twice to AT that supports ARIA?EDIT: I realize this is repeating what we did previously for D8.
EDIT2: More examples to look at here
https://www.davidmacd.com/test/using-wai-aria-in-html.html#rec-legend
https://www.smashingmagazine.com/2015/04/its-alive-apps-that-feed-back-a...
https://developer.paciellogroup.com/blog/2014/03/screen-reader-support-a...
Also if we use
role="status"we might also need to addaria-live="polite".I'm in the midst of comparing a great many examples of government status messages at the moment. There isn't a great general consensus on this.
Comment #20
sd9121 commented@andrewmacpherson,
I saw the previous patch got failed. I have rerolled the patch.
Thanks!
Comment #22
bnjmnmA duplicate of this issue was created a few months ago. I closed that one as there as there's more context here, but it may be worth consulting the most recent patch to see how the test failures in #15 can be avoided: #3159933: Status Message in Claro on theme install should have role="status" or similar instead of contentinfo
Comment #23
spokjeReroll and test failures fix of patch #15
Thanks @bnjmnm for pointing out the duplicate where I've
shamelessly copiedcarefully crafted the fixes from...Comment #24
spokjeAdded
Needs issue summary update-tag to address #18Because of that, keeping this at
Needs workalthough we have a working and green patch.Note: I think we don't need extra tests for this since we altered 4 tests covering this already.
Then again, I've been very wrong about tests before...
Comment #27
andy-blumNew patch is a re-roll of #23 for 9.3.x and adds the fix to Olivero's status-messages.html.twig file
Comment #28
spokjeComment #29
ankithashettyUpdated patch in #27 to fix custom command failures. Retaining status as "Needs work" for issue summary update as mentioned in #24 , thanks!
Comment #30
andy-blumUpdating issue summary's proposed resolution per #5, with a couple of alterations:
role="region"witharia-label="Status messages".role=alert, but without a clear rule or set of examples, I'm deciding to maintain the pattern we currently have where only error messages are given the alert role. mgifford suggested we addaria-live="polite"in #19, but I don't think that's necessary as the drupal.message API uses drupal.announce to announce new JS-based messages to users.Comment #31
mgiffordTagging this as a WCAG 1.3.1 issue.
Comment #32
rosk0Crosslinking, there is definitely an effort duplication between this issue and linked.
Comment #34
elusivemind commentedRe-rolled for the latest 9.3.9 (9.3.x)
Comment #35
chi commentedIs it a good idea to use UL and LI with
role="generic"when only one message needs to be printed? That would simplify styling as the markup is not changing.Comment #37
jayhuskinsI was unable to apply patch #34. It was rejected in the Olivero theme.
Comment #38
Munavijayalakshmi commentedComment #39
Munavijayalakshmi commentedRe-rolled patch for 9.5.x.
Comment #40
Munavijayalakshmi commentedComment #42
shubham chandra commentedRe-rolled pacth #29 in Drupal 10.1.x
Comment #43
ameymudras commentedTrying to fix CCF issue with patch #42
Comment #46
edmund.dunn commentedRerolled for 10.1. Applies cleanly.
Comment #47
edmoreta commented#46 it's not working
Comment #48
edmoreta commentedTests might fail and they will require extra work
Comment #49
edmoreta commentedComment #50
marvil07 commentedBack to NR for the new patches.
Comment #53
hardik_patel_12 commentedSolving failed test cases and rerolling for 11.x
Comment #54
smustgrave commentedCaused massive test failures.
Was previously tagged for issue summary, has that been completed.
Comment #55
hardik_patel_12 commentedRerolling to 11.x from #49.
Comment #58
prashant.cSubmitting Patch for 11.x and raising the MR as well.
Thank you.
Comment #59
smustgrave commentedLarge number of failures.
Also was previously tagged for issue summary update.
@Prashant.c there's no interdiff added what did you do different from #55
Comment #61
bnjmnmI opened MR 6112 as patches don't seem to be testing properly. Most of the 1800+ test failures in #55 and #58 do not seem to have anything to do with the code changes. Among other things there are "missing composer.lock" messages, and that file is not touched in either patch. The MR I created is failing ~3 tests, so those will need to be addressed, but that should be straightforward compared to the unexpectedly huge fail count that suddenly appeared (and not at all the fault of @Prashant.c or @Hardik_Patel_12)
Comment #62
hardik_patel_12 commented@bnjmnm, I have observed similar challenges in several other issues. When using the patch method, it leads to significant unrelated failures for the composer and other components, similar to the problems described in issues #55 and #58. However, the Merge Request (MR) method is functioning properly.
Comment #64
neclimdulBug in core's spellcheck step is blocking tests. #3418207: Spell-checking job fails with "couldn't find remote ref refs/heads/11.x" when 11.x branch does not exist in fork
Comment #65
bapplejax commentedSubmitting a re-roll of patch #58 to allow for a clean application to 10.3.x. Some syntax and line number changes.
Comment #66
bapplejax commentedthe 65-11.x.patch is a reroll to cleanly apply patch #58 to 10.3.x
Comment #67
2dareis2do commentedAgreed that there should not be more than one contentinfo per page.
https://www.w3.org/TR/wai-aria-1.2/#contentinfo
How about just using complementary role. Status, Error or Notice is often (not always) related to the page you are on.
https://www.w3.org/TR/wai-aria-1.2/#complementary
Complimentary also supports header and footer, which might be useful.
Comment #68
2dareis2do commentedLooking at output from Claro, I think that they have it about right. i.e. one message per notice.
Markup is output in a single region.
In theory I see no issue in giving
messages__wrapperormessages-listthe rolecontentinfoComment #69
2dareis2do commentedOne challenge here is css frameworks like bootstrap use a single role for all types of notifications. i.e.
role=alerthere is an example for warning.
To colour can also be changed. Here are some examples.
Certainly in Drupal, I would like to see the alerts beyond the standard status, warning and error. This would be useful for theming.
e.g. https://www.drupal.org/project/drupal/issues/3437924
Comment #70
2dareis2do commentedUpdated example of Olivero.
So it appears that notifications are grouped together for Olivero theme. This is different from Claro that has separate notifications.
Comment #71
2dareis2do commentedComment #72
2dareis2do commentedComment #73
2dareis2do commentedOk so it turns out message are displayed differently depending if big pipe is enabled.
If Big pipe is enabled messages are output
in a groupseparatelyIf Big Pipe is disabled, messages are output
separatelyas a groupComment #74
2dareis2do commentedWith Big Pipe enabled if there is a single message (claro) outputs role=contentinfo
With Big Pipe enabled
If message is single than one output is role=contentinfo
If output is more than one, output is role=status
I think error messages may be treated slightly differently.
Comment #75
2dareis2do commentedComment #76
2dareis2do commentedComment #77
2dareis2do commentedJust wanted to share how out looks with big pipe enabled in Drupal 10.2.7 (it's quite rare to see all types on a single page - see attached)
AFAICT messages are also grouped by type even with big pipe disabled.
Notice how there are multiple uses of
role="contentinfo"on a single page which is obviously wrong.In 10.3 I believe behaviour has changed slightly depending on whether big pipe is enable or not.
Comment #78
bapplejax commentedRerolling patch #65 for Drupal 10.3.8. Line number updates and removal of edits to a template file where changes were addressed.
Comment #79
kthullIn addition to the role, we should also change the `h2` element to a `div` element. Looking at Olivero on a stock Drupal install, the error message renders before the `h1` thereby breaking the heading order.
Comment #80
mgiffordIs it better to loose the semantics by dropping the H2 or to retain the heading order? I'm not sure.
Comment #81
cwilcox808 commentedMy inclination is to keep the h2s.
div role="region" aria-label="System messages", there's already semantics present that are useful for navigation. However, surveys show headings are used far more than landmarks by screen reader users for navigation.Comment #82
itmaybejj commentedI'd vote to keep the H2. Sub-themes might place this block in other regions.
Comment #83
kthullFailing WCAG checks is the current issue I'm facing, so from that point of view, it's problematic. An element with
role=statusalready gets announced as if it werearia-live=politewhich makes the h2 overkill.From Google search summary:
Comment #84
mgifford@kthull failing an automated test isn't the end of the world. There are often false positives. Of course it is easier if the automated tests pass.
Interesting to consider though if
role=statuswould be sufficient substitution. Worth looking into a bit more. Thanks!Comment #85
thamasMaybe I misunderstood something foundational, but as I see it, Drupal messages are generated on the server-side, and because of that, a whole page reload occurs before they are displayed. And reloading the page is "change of context" even if the "same" page is reloaded, isn't it?
So if this behaviour is "change of context", then it is totally irrelevant to use
role="status"becauseBesides the content of the technical definition, it is also important what happens in practice: when the page reloads, a screen reader just goes "back" to the beginning of the page and handles it as new content. This situation requires a different approach: the focus should be automatically/programmatically shifted to the message (which is not a "Status message" by definition).
Of course, messages can be added with AJAX, or JavaScript.
For JS, we have
Drupal.announce– that should be used.In case of AJAX, the current suggestion (define two separate containers with "status" and "alert") roles may be correct, as I understand. But it is important that these containers must exist when the page loads, and they should not be generated together with the message only! (Maybe this is how it is meant to be already, but it was not clear to me from the current issue definition/discussion.)
A small note on
role="error"*role="alert". This WCAG page says:Highlight from me. I'm not sure how serious "input error" is and what we can mean by "input error", but it is worth noting.
*There is no role="error" – I wrote this wrong initially. Adjusted based on comment #89.
Comment #86
mark_fullmerHrmm. I'm not sure that any reloading of a page is in fact a "change of context," especially when it's the same page reloaded. From https://www.w3.org/WAI/WCAG21/Understanding/status-messages#dfn-changes-... , there are multiple reasons why it might not constitute a change in context:
A Drupal page reload resulting in a new status message *is* made with user awareness, since it would be triggered by a user action.
The above seems more along the lines of a status message showing on the page, whereas the following example of a change in context does not sound like a status message showing on the page:
So it does seem that the Drupal status message meets the two criteria of the definition of a status message.
Comment #87
thamasThanks for your reply, Mark!
I was thinking about these too. And you may be right, and it is not a context change, reloading a page by the definition.
But that is why I did not end my comment with that.
When the page reloads, the browser rebuilds it from scratch (including the accessibility tree), right? If so that is a new page, new context even it contains the same things as before. Or am I wrong?
Anyway, it is just theory yet. :)
I have not seen any comments about testing the newer, suggested versions in practice with screen readers.
I have not done that myself either yet, but I plan to do it.
Comment #88
thamasSo I have not tested any of the patches above. Instead of that, I have updated the block--system-messages-block.html.twig template by adding
<div role="status" aria-atomic="true">around its content. Then I checked it with a screen reader (VoiceOver + Safari on Mac).The results:
When the page was reloaded before displaying the Status message the screen reader did NOT announce the message.
When the Status message was added by AJAX (the Frontend Editing contrib module's toggle switch does that) the screen reader DID announce the status message.
(Of course, more detailed testing with more tools would be fine, but these results support what I wrote before.)
Comment #89
cwilcox808 commentedWhen a page reloads (browser makes a page request and the server returns a page with the same URL), due to a form submission, following a link, etc. I would classify it as a change in context because focus changes, it's moved to the beginning of the page. Users expect a change in context when submitting a form or using a link.
@thamas Regarding your template update, role="status" implicitly has `aria-live="polite"` and `aria-atomic="true"` so neither attribute needs to be explicitly on the element. Also, correcting a previous message, there is no "error" role, that was meant to be "alert;" I think most messages classified as errors on Drupal are not input errors, certainly not the ones that only appear on page reload.
The purpose of role="status" is to create an ARIA live region, it's not meant to convey the nature of its contents or to aid in page navigation, other roles, like Landmark roles are for that. According to the ARIA specifications, status contents are not meant to be announced on page load, only when the live region contents change. I've found this to be the case in my testing. Navigating the page contents element-by-element, NVDA (with Chrome and Firefox) did not announce when a role="status" element was entered, exited, or even it was focused (even when the element had a name). When navigating using macOS 15.7.2 VoiceOver + Safari 26.1 it does choose to announce when the element was entered and exited when it had a name. Neither behavior is right or wrong, they're just different.
According to the ARIA specifications, `role="alert"` contents on page load should be announced but in practice I found they often aren't. macOS VoiceOver + Safari did not, NVDA + Firefox did not, NVDA + Chrome did, iOS 18.7.2 VoiceOver + Safari did but only the second alert when there were two alerts on the page (role="alert" is implicitly aria-live="assertive" which stops whatever a screen reader is saying to announce its contents, separate alerts are not queued).
Comment #90
thamasThanks for the information about real-world testing results @cwilcox808!
So, because the existing content of a live region is not announced after page reload (as that is not a content change of an existing page) I was thinking how it should be managed.
As I wrote before, when the page reloads before displaying itself, with the added message: "This situation requires a different approach: the focus should be automatically/programmatically shifted to the message (which is not a "Status message" by definition)."
I do not like that. And I was thinking: what if Drupal displayed all status messages with AJAX (even when the page needs a reload for any other reason)? This way, the message would be a content change of an existing page that can be handled by screen readers when it happens inside a live region (what is automatically created defining
role="status").Related feature in core: https://www.drupal.org/node/3086403 New MessageCommand allows adding messages from an Ajax response
(There used to be a contrib module forAJAX messages too: https://www.drupal.org/project/ajax_msgs)
What do you think about it?
Comment #91
thamasAddition to my previous comment: TIL that
htmxis now in Drupal core. So if myAJAXsuggestion above is the way, it can be built onhtmx.Comment #93
thamasComment #94
thamasSome notes to my "AJAX/htmx" messages idea by my teammate Artem Dmitriiev ("a.dmitriiev" here):
Comment #95
mgiffordSo time to update Drupal.announce()?
https://www.a11y-collective.com/blog/aria-live/
Comment #96
2dareis2do commentedJust a general observation around system messages. There is often a bit of a delay showing the notification and when it does appear there can be a bit of jump. If there are multiple notifications being shown this is more noticeable depending on your network connection, speed of the server, web browser etc.
Comment #97
mgiffordI stumbled on these issues when looking at the admin theme too https://www.drupal.org/project/drupal/issues/3587678
I like how the USWDS has done this:
https://designsystem.digital.gov/components/alert/
The UK has an issue in their issue queue from 2024 to implement something like this.
Comment #98
thamasThanks for the updates @mgifford. This topic is quite complex – but it would be nice to see some progress on it!