fix(styles): stop painting the disabled submit as a filled grey primary - #141
Merged
Merged
Conversation
The submit button on the sign-in screen was filled with --seamless-disabled while its label kept --seamless-accent-contrast. Those come from different places, so no value a themed app could supply worked in both a light and a dark theme, and the result read as a primary button that had broken rather than a control waiting on input. Fade the enabled pair instead. Label and background stay on the accent the app already tuned, so their contrast cannot invert with the theme. This is how the magic-link and passkey screens already draw disabled buttons. --seamless-disabled is no longer read anywhere and leaves the token table. Say why the button is refusing, too, in a live region below it. A disabled button is not focusable and is passed over by screen readers, so the refusal was silent for the people least able to guess the reason. That surfaced a related bug: a valid email typed in registration left the Login button enabled after switching to sign-in with the identifier field empty, because the submit check fell through to the registration field. Each mode now checks only its own field. Closes #131
Bccorb
force-pushed
the
fix/disabled-submit-reads-as-broken-primary
branch
from
September 4, 2026 01:14
f64609b to
066c3a4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #131
The problem
--seamless-disabledfilled the disabled submit button while its label kept--seamless-accent-contrast. The two colours are chosen in different places, sothe fill could not be tuned for one theme without breaking the other, and the
result read as a primary button that had broken rather than a control waiting on
input.
Measured against the fixed
#9ca3affill that shipped:The dark theme was the failing side, and the only lever available to a consuming
app (darkening
--seamless-disabled) would have broken the light theme's darklabel. Both figures sit below 4.5:1, which is expected: WCAG 1.4.3 exempts
inactive controls. The point is that after the change they are set by the same
pair, so one cannot invert without the other.
The change
Fade the enabled pair rather than swapping in a separate fill. The label and its
background stay on the accent the app already tuned. This matches how the
magic-link and passkey screens already draw their disabled buttons, and 0.6 is
the value
registerPasskey.module.cssuses. I compared 0.5, 0.6 and 0.7 on boththemes: 0.7 is close to indistinguishable from enabled, 0.5 washes out the light
theme's label.
.button:hoverbecame.button:hover:not(:disabled). Without that, a disabledbutton would still pick up the hover fill, which the old ordering happened to
suppress.
--seamless-disabledis no longer read anywhere. It is out of the token table,with a note telling anyone who set it to remove it. That is why the changeset is
minorrather thanpatch.Saying why it is disabled
The sign-in screen now reports whether the field is empty, incomplete, or ready,
in a
role="status"live region below the button and referenced byaria-describedby. A disabled button is not focusable and is passed over byscreen readers, so the refusal was previously silent for the people least able to
guess the reason.
A related bug this surfaced
canSubmit()was gated onmode === 'login' && identifier, so an emptyidentifier fell through to
isValidEmail(email), the registration field. Typing avalid email in registration and switching to sign-in left the Login button enabled
with the identifier box empty. No honest hint could be written on top of that, so
each mode now checks only its own field. Covered by a new test.
Notes for review
mfaLogin.module.cssis not imported anywhere today, consistent with therebeing no bundled MFA route, so it does not reach
dist. It carried the samerule and is fixed here so the bug does not ride along into that screen when it
lands, but that half of the diff cannot be verified by rendering.
dist/index.jswas checked after building: it carriesopacity:.6and nooccurrence of
seamless-disabled.Checks
npm run typecheck,npm run lint,npm run format:check,npm run buildandnpm run check-npm-buildall pass. 321 tests across 32 suites pass, includingfour new ones covering the hint states and the mode-switch bug.