From add1ee87aedece3892cc6da68ee85184e8430e9f Mon Sep 17 00:00:00 2001 From: Bernard Siebens Date: Mon, 13 Jul 2026 23:16:35 +0200 Subject: [PATCH] Put the otp input after its boxes A seventh box appeared when the field took focus. daisyUI positions each otp box with nth-child, which counts *every* child of the container, not just the spans. With the input as the first child, all six boxes shifted one stride right and the container matched :has(>span:nth-child(7)), so it grew to seven strides wide. The phantom box was the ::after active-box marker -- transparent until :focus-within gives it an outline -- sitting in the empty stride that the off-by-one had opened up. The input goes last, which is also how daisyUI's own examples order it. A test counts the boxes ahead of the input so this cannot come back. Co-Authored-By: Claude Opus 4.8 --- authentication/tests.py | 9 +++++++++ templates/allauth/elements/fields.html | 24 +++++++++++++++--------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/authentication/tests.py b/authentication/tests.py index 6edb60b..16a01b6 100644 --- a/authentication/tests.py +++ b/authentication/tests.py @@ -265,6 +265,15 @@ class TwoFactorPageTests(TestCase): self.assertContains(self.response, 'name="code"') self.assertContains(self.response, "otp otp-lg") + def test_the_input_comes_after_the_boxes(self): + # daisyUI places each box with nth-child, which counts every child. With the input + # first, all six boxes shift a stride right, the container grows to seven strides + # and the ::after focus marker appears as a phantom seventh box. + html = self.response.content.decode() + otp = html[html.index('class="otp otp-lg"') : html.index('name="code"')] + + self.assertEqual(otp.count(""), 6) + def test_the_otp_field_has_no_placeholder(self): # allauth sets placeholder="Code"; inside the boxes it reads as a typed-in code. self.assertNotContains(self.response, 'placeholder="Code"') diff --git a/templates/allauth/elements/fields.html b/templates/allauth/elements/fields.html index 163444a..a6de64a 100644 --- a/templates/allauth/elements/fields.html +++ b/templates/allauth/elements/fields.html @@ -43,20 +43,26 @@ {% with icon=field|field_icon %} {% if field|is_otp %} {% comment %} - daisyUI's otp is one input overlaid on the boxes, and it sizes itself from - its `span` children — so the sr-only label goes OUTSIDE the container, or it - would count as a seventh box and throw the width off. + daisyUI's otp is one input overlaid on the boxes. Child order is load-bearing: + it places each box with `nth-child`, which counts *every* child, not just the + spans — so the input must come LAST. Put it first and all six boxes shift one + stride right, the container matches :has(>span:nth-child(7)) and grows to + seven strides, and the ::after active-box marker (invisible until focus) shows + up in the gap as a phantom seventh box. + + For the same reason the sr-only label lives outside the container: another + child would throw the count off again. {% endcomment %} {% if attrs.unlabeled %}{% endif %}
+ + + + + + {{ field|as_otp }} - - - - - -
{% elif icon %}