Opened 8 weeks ago

Last modified 6 hours ago

#35870 assigned Bug

Allow customization of the blank option in select dropdowns

Reported by: Marijke Luttekes Owned by: Marijke Luttekes
Component: Forms Version: 5.0
Severity: Normal Keywords: accessibility
Cc: Marijke Luttekes Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Marijke Luttekes)

Select dropdown inputs (<select>) may have an empty first choice meant to leave a blank value or inform the user that they need to pick an option.

The default current value for this option is "---------" and cannot be customized for the entire project at once. A developer must override each form field individually if they wish to change this value on their website, which is tedious and prone to errors.

The empty choice value is defined as BLANK_CHOICE_DASH in django.db.models.fields.

Accessibility

The current blank option is also inaccessible; I quote this description from a private ticket that was assigned to me, and therefore cannot be linked directly:

Problem

Whenever a <select> dropdown has no option selected by default, a line of dashes is used inside of an <option>. The majority of screen reader/speech synthesiser combinations don't speak this, and so it can be confusing to hear essentially nothing. This is particularly true when navigating to the first choice from an option with text.

Solution

Use a more perceivable placeholder string for the first option, e.g. "(select an option)", using parentheses to disrupt first-letter keyboard navigation as little as possible.

Proposed change

I foresee two options:

  1. Replace BLANK_CHOICE_DASH with a new, optional setting, which allows users to set the blank option for their project. For backwards compatibility, the default value of the new setting equals "---------".
  2. Replace BLANK_CHOICE_DASH with a new, more descriptive value in core.

Option 1 will be less disruptive, as this will not break existing behaviors. Option 2 will fix this problem for all users, but it would require translations for every language we offer.

Change History (17)

comment:1 by Marijke Luttekes, 8 weeks ago

Description: modified (diff)
Summary: Allow customization of the empty option in select inputsAllow customization of the blank option in select dropdowns

comment:2 by Carlton Gibson, 8 weeks ago

Triage Stage: UnreviewedAccepted

Seems reasonable.

A couple of thoughts...

The default current value for this option is "---------" and cannot be customized for the entire project at once.

It's a module level constant so probably can be monkey patched as a workaround, in a settings file or a AppConfig.ready():

from django.db.models import fields

fields.BLANK_CHOICE_DASH = [("", "No value selected")

Given that this is form related, adding an attribute to the FORM_RENDERER would likely be a good candidate location, rather than a separate setting entirely.

Using the form renderer would also give us a location for deprecation of the older option (if that is agreed on) as we did similar with the move to the <div> form templates, providing a fallback renderer during the deprecation period.

I'll accept for both parts, assuming that *accessible by default* is a goal. Exact replacement for ----- TBD.

comment:3 by Marijke Luttekes, 8 weeks ago

Thanks, Carlton! I agree with using FORM_RENDERER to set a default value and also allow a user to override it in settings.

I have asked Thibaud if he wants to use this one for Djangonaut Space session 3. If not, I will pick it up.

comment:4 by Thibaud Colas, 8 weeks ago

Looking good :) I’ve started doing screen reader testing. Will report back once my testing is completed. This is so I can quantify this more precisely:

The majority of screen reader/speech synthesiser combinations don't speak this, and so it can be confusing to hear essentially nothing. This is particularly true when navigating to the first choice from an option with text.

Regarding the two options – the experience of the Django admin’s end users should take priority over that of Django developers, and contributors (translators). So if this is a problem for most screen reader users, it should be treated as a bug and fixed in the framework and fixed there so Django is accessible out of the box.

comment:5 by Thibaud Colas, 8 weeks ago

Owner: set to Thibaud Colas
Status: newassigned

comment:6 by Marijke Luttekes, 8 weeks ago

The options are not mutually exclusive, so I'd go for both.

Please take extra note of this part of the description:

[…] "(select an option)", using parentheses to disrupt first-letter keyboard navigation as little as possible.

The use of parentheses is essential to the new label.

(This feedback was provided by James Scholes, who is blind and an accessibility expert)

comment:7 by Carlton Gibson, 8 weeks ago

Not sure the monkey patch approach works...

It's used like this at class definition time...

def get_choices(
        self,
        include_blank=True,
        blank_choice=BLANK_CHOICE_DASH,
        limit_choices_to=None,
        ordering=(),
    ):

... so the blank_choice default is set before any monkey patch can apply.

Sorry for the bum lead there, 'twas but a hunch.

comment:8 by Claude Paroz, 8 weeks ago

I confirm that additional work for translators is a non-issue.

How about going to the Forum to obtain best practices for an accessible empty select option?

Could aria-label usage help here?

comment:9 by Marijke Luttekes, 8 weeks ago

How about going to the Forum to obtain best practices for an accessible empty select option?

The person who suggested (select an option) (James Scholes) is not only blind but also the head of accessibility of a company that specializes in inclusive design and accessibility. While I am not saying we shouldn't consult the forum, I highly value his professional opinion.

Could aria-label usage help here?

I advise against the use of aria-label, as it has some issues. Most notably, translation tools like Google Translate ignore these attributes completely. Alternatively, you can use aria-labelledby and point it to an element with regular text contents. (The same goes for aria-description versus aria-describedby).

comment:10 by jscholes, 8 weeks ago

I'm not sure about screen reader support levels for aria-label on <option>. But I'd recommend against it for two other reasons:

Firstly, with mark-up like <option aria-label="(select an option)">-----</option>, the accessible and visible labels would differ. WCAG auditors would probably disagree on whether that represented a failure of success criterion 2.5.3 (Label in Name), because the line of dashes is probably not a string that speech recognition users could speak out loud. Nevertheless, the actual name would be invisible.

This leads onto a more important second point: If developers were to override the default/placeholder text in code, Django would need to detect that and remove the aria-label. Otherwise, setting e.g. blank_choice="None" in Python would result in the HTML output <option aria-label="(select an option)">None</option>. This would definitely be a WCAG SC 2.5.3 failure, and a concrete accessibility bug because it would affect meaning.

If aria-label was only updated to match the defined default, it would arguably be an unnecessary use of ARIA at that point, with the potential problems described in aria-label is a code smell. In short, I think aria-label would introduce unwarranted complexity in this case.

As to the specific text of the default option: It doesn't have to be in parentheses, e.g. MDN uses a string surrounded by dashes in some of their examples. But it should begin with some sort of punctuation that users are unlikely to type, to avoid it participating in the typeahead functionality provided by the browser. E.g. <option>Choose one...</option> would receive focus when users typed the letter C. That may sometimes be what individual website developers want to happen, at which point they should be overriding the default anyway.

comment:11 by Claude Paroz, 8 weeks ago

Thanks James for this precious input. Indeed, I'd be in favor of --select an option-- instead of (select an option), but I guess it's about personal taste…

The semantic issue I can see with such a change is that for non-required selects, it might imply that choosing an option is somewhat required. Not a blocker, just a thought.

comment:12 by Marijke Luttekes, 7 weeks ago

Thibaud suggested I take this issue once he finishes his tests and reports the findings to this ticket.

I am still deciding how to fix this issue on the Python side. I intend to dive into the code and see what works.

My initial idea is:

  1. Create a new global setting, BLANK_CHOICE, with a default, translatable string value (possibly ).
  2. In the get_choices() method, make the blank_choice optional. Default it to None or an empty string (I am leaning towards None).
  3. When there is no custom value for blank_choice, default to the Django setting.

The default value may be a translatable string like (select an option) or ----- (multiple dashes) for backward compatibility. Due to the inaccessibility of dashes, we

Idea: Could we combine dashes and parentheses in the label? So --(select an option)--? That way, we have dashes as a visual cue and parentheses to improve keyboard navigation and screen reader support.

Feedback is appreciated.

comment:13 by Thibaud Colas, 4 weeks ago

Owner: changed from Thibaud Colas to Marijke Luttekes
Type: Cleanup/optimizationBug

Here are the results of my testing across six screen readers:

  1. NVDA 2024.4, Chrome: no announcement
  2. JAWS 2024, Chrome: "dash dash dash" (no matter how many dashes there are)
  3. Narrator 21H2, Microsoft Edge: "dash" sometimes, sometimes no announcement
  4. VoiceOver macOS 14.1, Safari: no announcement
  5. VoiceOver iOS 17, Safari: "nine hyphens"
  6. Talkback Android 11, Chrome: "nine dash"

Here’s the full recording: (YouTube) Django accessibility testing: dash dash dash (empty select widget). And if anyone wants to do their own testing, here’s my demo page: Django 5.2 admin change artist - Eno • Hyde.

So it’s only NVDA and VoiceOver macOS that have no announcement at all, but the "dash" announcements in other screen readers aren’t particularly helpful either. I would qualify this bug as a failure of WCAG SC 1.3.1 Info and Relationships.

Proposed changes

The above confirms a strong need to change the default. And we could also make this value more easily customize-able. Based on this testing, if we want to keep using dashes in combination with an intelligible label, I think the fewer dashes the better – one or two. A single dash/hyphen will probably be more likely to be skipped by screen readers. I’m not sure if this is a win or not. For example, I see the USWDS design system uses this: "- Select -". Perhaps "- Empty -", so we don’t confuse people that the field doesn’t necessarily need filling in as Claude mentioned?

Assigning back to you Marijke for you to proceed as you see fit.

comment:14 by Marijke Luttekes, 4 weeks ago

Below is a response I received from James through Slack, as his response was blocked by the spam filter for some reason:

Thanks, Thibaud, for all of this testing and reporting back of results.

I do want to say that it's unequivocally impossible to determine how a given screen reader will read punctuation. The configurability of screen readers largely renders that ineffective, including but not limited to:

  • language and locale;
  • speech engine;
  • voice;
  • preset or customised punctuation level;
  • verbosity setup; and
  • speech dictionary entries.

With that in mind, something like "- select -" could work very well, because it doesn't matter if the dashes are spoken explicitly, trigger a pause in speech, or are ignored. The meaning will be understood regardless. Indeed, it's probably better for most users if the dashes are not spoken, but only if some text is present to make up for it.

comment:15 by Thibaud Colas, 3 weeks ago

Excellent! Yes I forgot to mention in all cases that’s the default configuration of those screen readers.

I was also prevented by the spam filter from commenting with a link to WCAG SC 1.3.1 Info and Relationships. Not sure if it’s this topic that’s considered spam-likely, or a W3C link, or what.

comment:16 by Natalia Bidart, 3 weeks ago

Thank you everyone for the valuable conversation. Is Discourse accessible enough to have this conversation with everyone being able to participate, including James Scholes?
If yes, I would advice that the current designing and decision making would be, perhaps, better suited and reaching a wider audience in the Forum, inside the Accessibility category.

Personally, I would suggest that we do not create a new setting (which IIUC is the proposal in comment:12), I think instead we should pursue the proposal from Carlton to piggy back into FORM_RENDERER. Also I would vote against using parenthesis, and in favor of - Select an option - as formerly proposed.

comment:17 by Marijke Luttekes, 6 hours ago

Update: I created PR #18924 with a fix, assisted by Carlton.

We initially wanted to use a form renderer to set the value, but we had to resort to a setting.
The PR has details, but the short version is that not every implementation of a blank choice has access to the form renderer.

We concluded that this change is so impactful and close to the 5.2 feature freeze that we must postpone it until the 6.0 release.

I checked if we could make a separate change for 5.2 that would allow users to monkey-patch the label; sadly, this isn't an option.
Blank choice values get defined in multiple locations, including once in a place that makes it hard to import into several form-related modules (hence the multiple existing declarations).

So for now, we will have to put this issue on hold.

Note: See TracTickets for help on using tickets.
Back to Top