.input, .textarea and .select draw their edge with 1px solid var(--border) on a --surface-2 fill. Measured against the page ground, that boundary is between 1.22:1 and 1.50:1 in every theme and palette — WCAG 2.2 SC 1.4.11 Non-text Contrast (AA) asks for 3:1 on the visual information needed to identify a user interface component.
No border token in the system clears the bar, so this cannot be fixed by swapping one token for another.
Measured
| Theme |
Pair |
Ratio |
| Base dark |
--border #232c35 vs page #000000 |
1.48 |
| Base dark |
--border vs field --surface-2 #0a0d10 |
1.38 |
| Base dark |
--border-strong #313c48 vs page |
1.87 |
data-palette="wend" dark |
--border vs page #0d1117 |
1.34 |
data-palette="wend" dark |
--border vs field #151b24 |
1.22 |
data-palette="wend" dark |
--border-strong vs page |
1.69 |
data-palette="wend" dark |
field #151b24 vs page #0d1117 |
1.09 |
| Light |
--border #d8dee4 vs page #f6f8fa |
1.27 |
| Light |
--border vs field #ffffff |
1.36 |
| Light |
--border-strong #c5ced6 vs page |
1.50 |
For reference, --text-faint #87919c reaches 5.91:1 against the page and 5.40:1 against the field — but it is a text token, and reaching for it here would be a component borrowing a role that is not its own.
How it surfaced
In Wend (wendhq/wend#54). Its auth inputs had never carried a class at all, so they had been rendering with the browser's default control styling — a 2px #858585 inset border at 5.13:1. Adding class="input" fixed a target-size gap (32px → 51.59px) and, in the same stroke, dropped the boundary contrast from 5.13:1 to 1.34:1.
SC 1.4.11 exempts a control "where the appearance is determined by the user agent and not modified by the author". An unstyled input has that exemption. .input does not. So the design system is what decides whether a consumer conforms here, which is why this belongs upstream rather than in any one app's stylesheet.
The same argument applies to any interactive component whose only boundary is --border — the ghost and secondary button variants are the other case. Decorative edges on .card, .alert, .table and .toast are outside the SC and should not change.
Suggested direction
A dedicated control-boundary token — something like --control-border — set per theme and per palette to clear 3:1 against both the page ground and the component's own fill, with .input / .textarea / .select and the bordered button variants pointing at it. --border keeps its current value for the decorative cases, so nothing that is already correct moves.
Precedent: the 42px→44px control floor went the same route and reached consumers as bundle 2.0.2.
Decided
Malin, 2026-08-21: fix it here and sync out; no consumer-local override. Keeping consumers in sync is the point of the shared bundle, and an override in one app would leave every other consumer wrong while giving that app a second source of truth for a number the system already owns.
Tracked on the consumer side in wendhq/wend → docs/backlog.md, "A styled auth input has a 1.34:1 boundary against the page".
.input,.textareaand.selectdraw their edge with1px solid var(--border)on a--surface-2fill. Measured against the page ground, that boundary is between 1.22:1 and 1.50:1 in every theme and palette — WCAG 2.2 SC 1.4.11 Non-text Contrast (AA) asks for 3:1 on the visual information needed to identify a user interface component.No border token in the system clears the bar, so this cannot be fixed by swapping one token for another.
Measured
--border#232c35 vs page #000000--bordervs field--surface-2#0a0d10--border-strong#313c48 vs pagedata-palette="wend"dark--bordervs page #0d1117data-palette="wend"dark--bordervs field #151b24data-palette="wend"dark--border-strongvs pagedata-palette="wend"dark--border#d8dee4 vs page #f6f8fa--bordervs field #ffffff--border-strong#c5ced6 vs pageFor reference,
--text-faint#87919c reaches 5.91:1 against the page and 5.40:1 against the field — but it is a text token, and reaching for it here would be a component borrowing a role that is not its own.How it surfaced
In Wend (wendhq/wend#54). Its auth inputs had never carried a class at all, so they had been rendering with the browser's default control styling — a 2px
#858585inset border at 5.13:1. Addingclass="input"fixed a target-size gap (32px → 51.59px) and, in the same stroke, dropped the boundary contrast from 5.13:1 to 1.34:1.SC 1.4.11 exempts a control "where the appearance is determined by the user agent and not modified by the author". An unstyled input has that exemption.
.inputdoes not. So the design system is what decides whether a consumer conforms here, which is why this belongs upstream rather than in any one app's stylesheet.The same argument applies to any interactive component whose only boundary is
--border— the ghost and secondary button variants are the other case. Decorative edges on.card,.alert,.tableand.toastare outside the SC and should not change.Suggested direction
A dedicated control-boundary token — something like
--control-border— set per theme and per palette to clear 3:1 against both the page ground and the component's own fill, with.input/.textarea/.selectand the bordered button variants pointing at it.--borderkeeps its current value for the decorative cases, so nothing that is already correct moves.Precedent: the 42px→44px control floor went the same route and reached consumers as bundle 2.0.2.
Decided
Malin, 2026-08-21: fix it here and sync out; no consumer-local override. Keeping consumers in sync is the point of the shared bundle, and an override in one app would leave every other consumer wrong while giving that app a second source of truth for a number the system already owns.
Tracked on the consumer side in
wendhq/wend→docs/backlog.md, "A styled auth input has a 1.34:1 boundary against the page".