Telephone and web address rows: say what it is first, then the value #213

Closed
opened 2026-09-15 19:39:59 +00:00 by tiagoagueda · 2 comments
Owner

In the rows for telephone numbers, social profiles, code repositories and websites, the fields should come in the order a person decides them: first what the entry is, then the value itself. Today every one of these rows puts the value first and describes it afterwards.

Telephone numbers:

  1. Kind (Mobile, Work, Home, Switchboard, Fax, Other)
  2. Name, shown only when Kind is Other
  3. Country
  4. Number

Social profiles, code repositories and websites:

  1. Name (optional: "LinkedIn", "Codeberg", "my blog")
  2. Address

Then, in both kinds of row, the Primary / Gets me back in / Remove controls.

What it is today

Telephone numbers

src/postulo/templates/partials/phone_numbers.html, included by Your details (accounts/profile.html:121) and the contact form (jobs/contact_form.html:38) when Several telephone numbers is on:

  • Line 1 (:33-36): Phone, which is the PhoneField widget (partials/phone_widget.html): the country select with its flag, then the number box.
  • Line 2 (:37-83): a three-column grid holding Kind, Name, if Other, and the Primary / Gets me back in / Remove controls.

The form declares fields = ("kind", "label", "number") (core/phone_numbers.py:297), but the template draws them in its own order. Name, if Other is always visible, even though it only means something for Other. The server already requires it in that case (PhoneNumberForm.clean, :313-317), and nothing requires or uses it otherwise.

Social profiles, code repositories and websites

src/postulo/templates/partials/web_links.html (#189) draws one block per kind, each only while its feature is on, on Your details and on the contact form:

  • Line 1 (:26-29): Address (url), full width.
  • Line 2 (:30-47): a two-column grid with Name (label) and the Primary / Remove controls.

These rows have no kind field: each block is a formset of one kind, and the block's legend says which (core/web_links.py:60-124, WebLinkForm.fields = ("label", "url") at :295). Name is optional and always meaningful (left blank, the address's host is shown instead, per the note at :55-60 and the model's help text at core/models.py:537), so nothing about it is conditional. The change is only the order: Name, then Address on its own full-width line, then the controls.

Why it is the way it is

A comment in phone_numbers.html (:28-32) says the number was given its own line deliberately. Squeezed into a column beside two others, the fixed 10rem country select left the number box a few pixels wide, which is unusable and a WCAG 2.2 2.5.8 target-size failure that axe caught. The new order must not bring that back: country and number stay together as one full-width control (they are one widget already, country first), below kind and name, not squeezed beside them.

web_links.html (:23-25) gives the same reason for the address: it is the long thing in the row. That still holds, so the address keeps its own full-width line, now below the name.

What a fix has to settle

  • Order in phone_numbers.html: a line for Kind (and Name when shown), then the phone widget on its own line, then Primary / recovery / Remove. The DOM order is the reading and tab order, so no order-* utilities.
  • Order in web_links.html: Name on one line, then Address on its own full-width line, then Primary / Remove. There is no kind to add and nothing to show or hide.
  • One arrangement for both. The two partials should end up the same shape — the description, the value on its own line, the controls — so a page with both kinds of row reads consistently.
  • Showing Name only for Other, without breaking scripts-off use:
    • The server renders the field hidden unless the row's kind is already Other, either on load or after a failed submit where the person chose Other.
    • app.js shows and hides it as Kind changes, delegated from the document like the rest of the file (the CSP forbids inline handlers), and moves focus nowhere.
    • With scripts blocked the field must still be reachable, or a person choosing Other cannot give the name the server then demands. Either leave it visible when the script has not run (the script hides it, the template does not), or give a <noscript> fallback. The first is simpler and matches how the dragging code sets draggable only from script.
    • Hidden means hidden, so it is out of the accessibility tree too. When it appears, it follows Kind in the reading order, so a screen reader meets it next without any announcement.
  • The label can then drop "if Other" and read Name, since it only appears when that is true. Keep the model's verbose_name and help text in step (core/models.py:180-183); that is a translation change in every catalogue.
  • A value typed under Other and then abandoned (kind changed away from Other): decide whether it is cleared on save or kept silently. Clearing it matches what the field means.
  • Layout on a phone: everything stacks in the new order, and nothing sits beside the phone widget below sm:.
  • The single boxes are unaffected: the one telephone field while Several telephone numbers is off (accounts/forms.py:301-312) and the one URL box per kind while its Several … feature is off (core/web_links.py:248-267) have no kind or name to reorder.

The same shape elsewhere

These rows have the same Kind + Name, if Other pair:

  • postal addresses (partials/postal_addresses.html:63);
  • the identifier rows on Your details (accounts/profile.html:153);
  • company identifiers (jobs/company_form.html:80).

The show-only-for-Other behaviour belongs in one place in app.js (e.g. a data-shows-for-kind="other" attribute on the name field's wrapper) so they can adopt it. Whether they also change order should be decided here and filed separately, not done quietly as part of this.

Checks

  • Tests:
    • a telephone row rendered with kind Other shows the name field, and a row with any other kind renders it with hidden; a failed submit with Other and no name shows it with its error;
    • in both partials, the description's field comes before the value's field in the rendered HTML (kind before number; name before address).
  • Browser suite: tests/e2e/test_accessibility.py visits Your details and the contact form with all three link features on; keep axe clean at phone width, and add a step that picks Other and finds the field.
  • CSS: npm run build:css if new classes appear.
  • Translations: scripts/messages.py extract for the changed label.
In the rows for telephone numbers, social profiles, code repositories and websites, the fields should come in the order a person decides them: first what the entry *is*, then the value itself. Today every one of these rows puts the value first and describes it afterwards. **Telephone numbers:** 1. **Kind** (Mobile, Work, Home, Switchboard, Fax, Other) 2. **Name**, shown **only when Kind is *Other*** 3. **Country** 4. **Number** **Social profiles, code repositories and websites:** 1. **Name** (optional: "LinkedIn", "Codeberg", "my blog") 2. **Address** Then, in both kinds of row, the *Primary* / *Gets me back in* / *Remove* controls. ## What it is today ### Telephone numbers `src/postulo/templates/partials/phone_numbers.html`, included by *Your details* (`accounts/profile.html:121`) and the contact form (`jobs/contact_form.html:38`) when *Several telephone numbers* is on: - **Line 1 (`:33-36`):** *Phone*, which is the `PhoneField` widget (`partials/phone_widget.html`): the country select with its flag, then the number box. - **Line 2 (`:37-83`):** a three-column grid holding *Kind*, *Name, if Other*, and the Primary / *Gets me back in* / Remove controls. The form declares `fields = ("kind", "label", "number")` (`core/phone_numbers.py:297`), but the template draws them in its own order. *Name, if Other* is always visible, even though it only means something for *Other*. The server already requires it in that case (`PhoneNumberForm.clean`, `:313-317`), and nothing requires or uses it otherwise. ### Social profiles, code repositories and websites `src/postulo/templates/partials/web_links.html` (#189) draws one block per kind, each only while its feature is on, on *Your details* and on the contact form: - **Line 1 (`:26-29`):** *Address* (`url`), full width. - **Line 2 (`:30-47`):** a two-column grid with *Name* (`label`) and the Primary / Remove controls. These rows have **no kind field**: each block is a formset of one kind, and the block's legend says which (`core/web_links.py:60-124`, `WebLinkForm.fields = ("label", "url")` at `:295`). *Name* is optional and always meaningful (left blank, the address's host is shown instead, per the note at `:55-60` and the model's help text at `core/models.py:537`), so nothing about it is conditional. The change is only the order: *Name*, then *Address* on its own full-width line, then the controls. ## Why it is the way it is A comment in `phone_numbers.html` (`:28-32`) says the number was given its own line deliberately. Squeezed into a column beside two others, the fixed 10rem country select left the number box a few pixels wide, which is unusable and a WCAG 2.2 2.5.8 target-size failure that axe caught. The new order must not bring that back: country and number stay together as one full-width control (they are one widget already, country first), below kind and name, not squeezed beside them. `web_links.html` (`:23-25`) gives the same reason for the address: it is the long thing in the row. That still holds, so the address keeps its own full-width line, now below the name. ## What a fix has to settle - **Order in `phone_numbers.html`:** a line for *Kind* (and *Name* when shown), then the phone widget on its own line, then Primary / recovery / Remove. The DOM order is the reading and tab order, so no `order-*` utilities. - **Order in `web_links.html`:** *Name* on one line, then *Address* on its own full-width line, then Primary / Remove. There is no kind to add and nothing to show or hide. - **One arrangement for both.** The two partials should end up the same shape — the description, the value on its own line, the controls — so a page with both kinds of row reads consistently. - **Showing Name only for Other, without breaking scripts-off use:** - The server renders the field hidden unless the row's kind is already *Other*, either on load or after a failed submit where the person chose *Other*. - `app.js` shows and hides it as *Kind* changes, delegated from the document like the rest of the file (the CSP forbids inline handlers), and moves focus nowhere. - **With scripts blocked the field must still be reachable,** or a person choosing *Other* cannot give the name the server then demands. Either leave it visible when the script has not run (the script hides it, the template does not), or give a `<noscript>` fallback. The first is simpler and matches how the dragging code sets `draggable` only from script. - Hidden means `hidden`, so it is out of the accessibility tree too. When it appears, it follows *Kind* in the reading order, so a screen reader meets it next without any announcement. - **The label** can then drop "if Other" and read *Name*, since it only appears when that is true. Keep the model's `verbose_name` and help text in step (`core/models.py:180-183`); that is a translation change in every catalogue. - **A value typed under Other and then abandoned** (kind changed away from *Other*): decide whether it is cleared on save or kept silently. Clearing it matches what the field means. - **Layout on a phone:** everything stacks in the new order, and nothing sits beside the phone widget below `sm:`. - **The single boxes** are unaffected: the one telephone field while *Several telephone numbers* is off (`accounts/forms.py:301-312`) and the one URL box per kind while its *Several …* feature is off (`core/web_links.py:248-267`) have no kind or name to reorder. ## The same shape elsewhere These rows have the same *Kind* + *Name, if Other* pair: - postal addresses (`partials/postal_addresses.html:63`); - the identifier rows on *Your details* (`accounts/profile.html:153`); - company identifiers (`jobs/company_form.html:80`). The show-only-for-Other behaviour belongs in one place in `app.js` (e.g. a `data-shows-for-kind="other"` attribute on the name field's wrapper) so they can adopt it. Whether they also change order should be decided here and filed separately, not done quietly as part of this. ## Checks - **Tests:** - a telephone row rendered with kind *Other* shows the name field, and a row with any other kind renders it with `hidden`; a failed submit with *Other* and no name shows it with its error; - in both partials, the description's field comes before the value's field in the rendered HTML (kind before number; name before address). - **Browser suite:** `tests/e2e/test_accessibility.py` visits *Your details* and the contact form with all three link features on; keep axe clean at phone width, and add a step that picks *Other* and finds the field. - **CSS:** `npm run build:css` if new classes appear. - **Translations:** `scripts/messages.py extract` for the changed label.
tiagoagueda changed title from Telephone number rows: kind first, then a name only for Other, then country and number to Telephone and web address rows: say what it is first, then the value 2026-09-15 19:42:18 +00:00
tiagoagueda added this to the 0.6.0 milestone 2026-09-15 21:33:34 +00:00
tiagoagueda modified the milestone from 0.6.0 to 0.5.0 2026-09-29 11:27:05 +00:00
Author
Owner

Moved to 0.5.0. The interface review of 2026-09-29 asked for this order again, with two additions:

  • On a wide screen, the kind sits on one line with the value: a telephone number's kind beside the number, and a web link's service beside its address.
  • Postal addresses put their kind and Primary first as well: the shape this issue's last section said should be decided separately.

The rest of that review, for the same rows, is filed on its own:

  • #303: Primary and Remove as icons, and Remove confirmed in a dialog and done at once.
  • #304: a telephone number's country and number as one input group, checked against the country's numbering plan, and shown grouped the way that country writes it.
  • #305: a web link's kind becomes the service it is on (LinkedIn, Codeberg…), with an icon, and the address is checked against it. On a web link, the "name first" of this issue is that service.
  • #306: postal addresses checked against their country.
  • #307: an identifier kind already recorded cannot be chosen again.
  • #301: the kind chosen in Basecoat's select, with an icon per kind.

The Name shown only for Other landed with #284.

Moved to 0.5.0. The interface review of 2026-09-29 asked for this order again, with two additions: - **On a wide screen, the kind sits on one line with the value**: a telephone number's kind beside the number, and a web link's service beside its address. - **Postal addresses put their kind and *Primary* first** as well: the shape this issue's last section said should be decided separately. The rest of that review, for the same rows, is filed on its own: - #303: *Primary* and *Remove* as icons, and *Remove* confirmed in a dialog and done at once. - #304: a telephone number's country and number as one input group, checked against the country's numbering plan, and shown grouped the way that country writes it. - #305: a web link's kind becomes the service it is on (LinkedIn, Codeberg…), with an icon, and the address is checked against it. On a web link, the "name first" of this issue is that service. - #306: postal addresses checked against their country. - #307: an identifier kind already recorded cannot be chosen again. - #301: the kind chosen in Basecoat's select, with an icon per kind. The *Name* shown only for *Other* landed with #284.
Author
Owner

Done, by the issues the interface review split this into. All of it is on main and green
(f09b6010b).

Telephone numbers (#304, f91eaa5a0). A row says what it is before the number: the kind,
then the name, which is drawn only while the kind is Other (scripts on or off), then the
country and the number as one group. On a wide screen the kind sits on one line with the
country and the number
, from 768 pixels: measured in the font CI draws in, the kind's
column and a group wide enough to keep its chooser and its box together need 516 pixels of
row, which a row does not have at 640. A row that shows its name has the kind and the name
on that line and the group on the next, because the three do not fit inside the card at any
width. On a phone each has a line of its own.

Web links (#305, 307f090d2). These rows had no kind; they have the service now
(LinkedIn, Codeberg, Other…), with its icon, first in the row; then the name, only under
Other; then the address; on one wrapping line from 640 pixels and a column below it.

Postal addresses (#306, 58ba714dd). The kind first, the name only for Other, then
the address in its country's own order.

Primary and Remove are the star and the bin at the end of every row, the same place on
all three, as this issue's order put them ("then, in both kinds of row, the Primary / Gets
me back in / Remove controls") and #303 drew them. #306's own text resolves "the kind and
Primary first" for addresses as "the order #213 describes", which is this one. If the
star should lead the row instead, that is a small change to three templates: reopen this
and say so.

Also from this issue's list: the name box follows the kind's select from the stylesheet
(data-if-other), so there is one rule for every row and no script is needed for it; the
identifier rows already had it (#284). Gets me back in is offered on Your details only,
never on a contact's rows (#650).

Done, by the issues the interface review split this into. All of it is on `main` and green (`f09b6010b`). **Telephone numbers** (#304, `f91eaa5a0`). A row says what it is before the number: the kind, then the name, which is drawn only while the kind is *Other* (scripts on or off), then the country and the number as one group. **On a wide screen the kind sits on one line with the country and the number**, from 768 pixels: measured in the font CI draws in, the kind's column and a group wide enough to keep its chooser and its box together need 516 pixels of row, which a row does not have at 640. A row that shows its name has the kind and the name on that line and the group on the next, because the three do not fit inside the card at any width. On a phone each has a line of its own. **Web links** (#305, `307f090d2`). These rows had no kind; they have the **service** now (LinkedIn, Codeberg, *Other*…), with its icon, first in the row; then the name, only under *Other*; then the address; on one wrapping line from 640 pixels and a column below it. **Postal addresses** (#306, `58ba714dd`). The kind first, the name only for *Other*, then the address in its country's own order. **Primary and Remove** are the star and the bin at the end of every row, the same place on all three, as this issue's order put them ("then, in both kinds of row, the Primary / Gets me back in / Remove controls") and #303 drew them. #306's own text resolves "the kind and *Primary* first" for addresses as "the order #213 describes", which is this one. If the star should lead the row instead, that is a small change to three templates: reopen this and say so. Also from this issue's list: the name box follows the kind's select from the stylesheet (`data-if-other`), so there is one rule for every row and no script is needed for it; the identifier rows already had it (#284). *Gets me back in* is offered on *Your details* only, never on a contact's rows (#650).
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#213
No description provided.