Choices in Basecoat's select: a flag beside a language or a country, an icon beside a kind, time zones grouped by continent #301

Closed
opened 2026-09-29 11:26:35 +00:00 by tiagoagueda · 2 comments
Owner

From the interface review of 2026-09-29. The rule asked for: a select is Basecoat's select unless a page says otherwise, and the places where the list itself should show more than words:

  • A flag beside each language's name: the language of your career record in Your details → Contact block, Settings → Language and time, Server settings → Defaults, and a CV's language in its settings.
  • A flag beside each country: a postal address's country, and the country in front of a telephone number.
  • An icon beside each kind: a telephone number's kind (mobile, work, home, switchboard, fax, other), a postal address's kind, an identifier's kind, the service a web link is on (#305), and an application's status on the board's cards (#315).
  • Time zones grouped by continent, under Settings → Language and time and Server settings → Defaults.

What is there today

  • #290 adopted Basecoat's native-select: every <select> is the browser's own, painted. It works with scripts off and with every assistive technology, and on a phone it opens the platform's own picker.
  • An <option> holds text and nothing else, which is why #88 and #208 put the flag beside the closed select ({% language_flag %}, {% flag %}, data-flag-holder), drawn by the server and kept in step by app.js. In the open list there is no flag, and there is no icon anywhere.
  • accounts/forms.py::time_zone_choices is one list of every IANA name, sorted: some four hundred entries.

What Basecoat's select is

select.css with select.js: a <button> showing the choice, a popover holding a role="listbox", a hidden <input> carrying the value, and a filter box for a long list. Its options are elements, so a flag or an icon can sit in them.

It does nothing without its script: the button opens nothing and the form posts no value. Every page here works with scripts off, so the server draws the native <select> and the script replaces it with Basecoat's markup, carrying across the value, required, disabled, the label's for, aria-describedby and the errors, with the form posting the same name either way.

Decisions to take

  1. Everywhere, or where the list has something to show. The request says everywhere. The cost: a phone loses its native picker for a yes/no or a four-item list, and every form needs the script. A narrower rule would still give what was asked for wherever it shows: Basecoat's select where the options carry a flag or an icon, or where a list is long enough to want a filter.
  2. Vendoring Basecoat's select.js, or writing it into app.js. The project did not vendor Basecoat's dropdown-menu script, because it needed a <button> trigger and hid the panel until it ran. The same test applies here.
  3. Icons for brands. Some web-link services (LinkedIn, GitHub) and some identifier schemes (ORCID) have logos, not icons. Lucide carries no brand marks, and TRADEMARKS.md says what the project does with other people's marks. Proposal: a generic icon per kind, and a brand mark only where its owner's terms allow it.
  4. Grouping time zones needs no new control. <optgroup> by the part of the IANA name before the slash (Africa, America, Antarctica, Asia, Atlantic, Australia, Europe, Indian, Pacific, and Etc for the rest) works in the native select today, and Basecoat's select has groups too.

Done when

  • The component exists with its scripts-off path, and every place listed above uses it.
  • Keyboard: type-ahead, the arrow keys, Home and End, Escape. A screen reader announces the listbox, its options and the choice. axe is clean in both themes, and the page reflows at 320 pixels in English, German and Greek.
  • tests/test_template_lint.py holds whatever rule (1) decides.
  • CONTRIBUTING.md and the wiki's Design page say when a select is Basecoat's.
From the interface review of 2026-09-29. The rule asked for: **a select is Basecoat's select unless a page says otherwise**, and the places where the list itself should show more than words: - **A flag beside each language's name**: the language of your career record in *Your details → Contact block*, *Settings → Language and time*, *Server settings → Defaults*, and a CV's language in its settings. - **A flag beside each country**: a postal address's country, and the country in front of a telephone number. - **An icon beside each kind**: a telephone number's kind (mobile, work, home, switchboard, fax, other), a postal address's kind, an identifier's kind, the service a web link is on (#305), and an application's status on the board's cards (#315). - **Time zones grouped by continent**, under *Settings → Language and time* and *Server settings → Defaults*. ## What is there today - #290 adopted Basecoat's `native-select`: every `<select>` is the browser's own, painted. It works with scripts off and with every assistive technology, and on a phone it opens the platform's own picker. - An `<option>` holds text and nothing else, which is why #88 and #208 put the flag *beside* the closed select (`{% language_flag %}`, `{% flag %}`, `data-flag-holder`), drawn by the server and kept in step by `app.js`. In the open list there is no flag, and there is no icon anywhere. - `accounts/forms.py::time_zone_choices` is one list of every IANA name, sorted: some four hundred entries. ## What Basecoat's select is `select.css` with `select.js`: a `<button>` showing the choice, a popover holding a `role="listbox"`, a hidden `<input>` carrying the value, and a filter box for a long list. Its options are elements, so a flag or an icon can sit in them. **It does nothing without its script**: the button opens nothing and the form posts no value. Every page here works with scripts off, so the server draws the native `<select>` and the script replaces it with Basecoat's markup, carrying across the value, `required`, `disabled`, the label's `for`, `aria-describedby` and the errors, with the form posting the same name either way. ## Decisions to take 1. **Everywhere, or where the list has something to show.** The request says everywhere. The cost: a phone loses its native picker for a yes/no or a four-item list, and every form needs the script. A narrower rule would still give what was asked for wherever it shows: Basecoat's select where the options carry a flag or an icon, or where a list is long enough to want a filter. 2. **Vendoring Basecoat's `select.js`, or writing it into `app.js`.** The project did not vendor Basecoat's dropdown-menu script, because it needed a `<button>` trigger and hid the panel until it ran. The same test applies here. 3. **Icons for brands.** Some web-link services (LinkedIn, GitHub) and some identifier schemes (ORCID) have logos, not icons. Lucide carries no brand marks, and `TRADEMARKS.md` says what the project does with other people's marks. Proposal: a generic icon per kind, and a brand mark only where its owner's terms allow it. 4. **Grouping time zones needs no new control.** `<optgroup>` by the part of the IANA name before the slash (Africa, America, Antarctica, Asia, Atlantic, Australia, Europe, Indian, Pacific, and `Etc` for the rest) works in the native select today, and Basecoat's select has groups too. ## Done when - The component exists with its scripts-off path, and every place listed above uses it. - Keyboard: type-ahead, the arrow keys, Home and End, Escape. A screen reader announces the listbox, its options and the choice. axe is clean in both themes, and the page reflows at 320 pixels in English, German and Greek. - `tests/test_template_lint.py` holds whatever rule (1) decides. - `CONTRIBUTING.md` and the wiki's *Design* page say when a select is Basecoat's.
tiagoagueda added this to the 0.5.0 milestone 2026-09-29 11:26:35 +00:00
Author
Owner

Decided (2026-09-29): everywhere, as the review asked, not only where the options carry a flag or an icon. The server still draws the native <select> as the scripts-off path, and the script replaces it with Basecoat's. So decision 1 is settled; tests/test_template_lint.py holds the rule that a select is Basecoat's.

**Decided (2026-09-29): everywhere**, as the review asked, not only where the options carry a flag or an icon. The server still draws the native `<select>` as the scripts-off path, and the script replaces it with Basecoat's. So decision 1 is settled; `tests/test_template_lint.py` holds the rule that a select is Basecoat's.
Author
Owner

The maintainer's list of 2026-10-02 adds a requirement that bears on this issue: most select fields do not respect the dark theme.

Two new issues link here. #655 records what the stylesheet does today: the themed option list covers only .field > select and select.select (src/postulo/static/css/app.css:3022-3039), so native selects outside .field > leave the open list to the browser. It keeps the native select as the scripts-off path, as decided on 2026-09-29, so it stays true after this issue lands. #654 takes up brand icons (Simple Icons); its decision 3 is the one this issue still has open.

The acceptance should therefore read: axe is clean in both themes, including selects outside .field.

From the list of improvements of 2026-10-02.

The maintainer's list of 2026-10-02 adds a requirement that bears on this issue: most select fields do not respect the dark theme. Two new issues link here. #655 records what the stylesheet does today: the themed option list covers only `.field > select` and `select.select` (`src/postulo/static/css/app.css:3022-3039`), so native selects outside `.field >` leave the open list to the browser. It keeps the native select as the scripts-off path, as decided on 2026-09-29, so it stays true after this issue lands. #654 takes up brand icons (Simple Icons); its decision 3 is the one this issue still has open. The acceptance should therefore read: axe is clean in both themes, including selects outside `.field`. From the list of improvements of 2026-10-02.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#301
No description provided.