Plugin switches in right-to-left: the knob is drawn outside its track, so on and off differ only by colour #517

Closed
opened 2026-10-01 10:47:20 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the switch's paint and rendering the compiled stylesheet in headless Chromium under dir="ltr" and dir="rtl".

Checked again on 2026-10-01 against main at 0a5cad587: still present.

The switch's knob travels by a physical translation, so in a right-to-left page it moves the wrong way. Basecoat lays the knob out as the first flex item of an inline-flex input, which puts it against the inline-start edge: the left in English, the right in Arabic. The paint then pushes it right in both directions. Under rtl the knob of a switch that is off overhangs the track's edge by 2px, and the knob of a switch that is on sits entirely outside the track. In the rendering the on-state knob was drawn over the neighbouring control.

  • Where: assets/css/basecoat.css:169-173 (.input[type="checkbox"][role="switch"]: before:translate-x-0.5 and checked:before:translate-x-4); the structure is Basecoat's components/switch.css (inline-flex … items-center, before:block). Used by templates/server/plugins.html:83 and templates/server/person_plugins.html:74.
  • What happens: a staff member whose language is Arabic (ar is in core/languages.py and base.html:2 sets dir from it) opens Server settings → Plugins or a person's plugins. The computed translate of the knob is 2px off and 16px on in both directions. Every switch that is on shows a filled track with no knob in it and a white knob beside it; every switch that is off shows its knob hanging over the track's end.
  • Why it matters: the knob's position is the switch's non-colour cue for its state. Without it, on and off are told apart by the track's colour alone (WCAG 2.2 SC 1.4.1, Use of Colour), and the controls look broken. tests/e2e/test_direction.py walks /server/plugins/ under rtl but only runs axe, which does not measure where a pseudo-element is drawn. The physical-side lint in tests/test_template_lint.py covers margins, padding, insets, borders and text alignment, not translate-x.
  • Fix: make the travel follow the reading direction, for example by adding rtl:before:-translate-x-0.5 rtl:checked:before:-translate-x-4 to the same @apply, or by placing the knob with a logical property (margin-inline-start) instead of a translation. Consider adding translate-x on direction-dependent components to the physical-side lint, or listing it in the stylesheet's comment as the exception that needs an rtl: partner.

Done when

  • A browser test in tests/e2e/test_direction.py signs in with the language set to ar, opens /server/plugins/, and asserts for one switch that is on and one that is off that the knob's box (read from the ::before geometry, or from a screenshot region) lies inside the input's box. It fails today and passes after the fix.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the switch's paint and rendering the compiled stylesheet in headless Chromium under `dir="ltr"` and `dir="rtl"`. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The switch's knob travels by a physical translation, so in a right-to-left page it moves the wrong way. Basecoat lays the knob out as the first flex item of an `inline-flex` input, which puts it against the inline-start edge: the left in English, the right in Arabic. The paint then pushes it right in both directions. Under `rtl` the knob of a switch that is off overhangs the track's edge by 2px, and the knob of a switch that is on sits entirely outside the track. In the rendering the on-state knob was drawn over the neighbouring control. - **Where:** `assets/css/basecoat.css:169-173` (`.input[type="checkbox"][role="switch"]`: `before:translate-x-0.5` and `checked:before:translate-x-4`); the structure is Basecoat's `components/switch.css` (`inline-flex … items-center`, `before:block`). Used by `templates/server/plugins.html:83` and `templates/server/person_plugins.html:74`. - **What happens:** a staff member whose language is Arabic (`ar` is in `core/languages.py` and `base.html:2` sets `dir` from it) opens *Server settings → Plugins* or a person's plugins. The computed `translate` of the knob is `2px` off and `16px` on in both directions. Every switch that is on shows a filled track with no knob in it and a white knob beside it; every switch that is off shows its knob hanging over the track's end. - **Why it matters:** the knob's position is the switch's non-colour cue for its state. Without it, on and off are told apart by the track's colour alone (WCAG 2.2 SC 1.4.1, Use of Colour), and the controls look broken. `tests/e2e/test_direction.py` walks `/server/plugins/` under `rtl` but only runs axe, which does not measure where a pseudo-element is drawn. The physical-side lint in `tests/test_template_lint.py` covers margins, padding, insets, borders and text alignment, not `translate-x`. - **Fix:** make the travel follow the reading direction, for example by adding `rtl:before:-translate-x-0.5 rtl:checked:before:-translate-x-4` to the same `@apply`, or by placing the knob with a logical property (`margin-inline-start`) instead of a translation. Consider adding `translate-x` on direction-dependent components to the physical-side lint, or listing it in the stylesheet's comment as the exception that needs an `rtl:` partner. ## Done when - A browser test in `tests/e2e/test_direction.py` signs in with the language set to `ar`, opens `/server/plugins/`, and asserts for one switch that is on and one that is off that the knob's box (read from the `::before` geometry, or from a screenshot region) lies inside the input's box. It fails today and passes after the fix.
tiagoagueda added this to the 0.5.0 milestone 2026-10-01 10:47:20 +00:00
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#517
No description provided.