diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ca7cf7d..a2223e9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Breaking Changes - **components:** `.sf-btn` nested in `.sf-card` no longer auto-shrinks its label to `--sf-text-s`. Buttons in cards now render at their own size like anywhere else; add `.sf-btn--s` for a compact card action. The orphaned `--sf-card-btn-font-size` token is removed. See `docs/migration.md`. +- **forms:** form fields no longer auto-colour on native `:user-invalid` / `:user-valid` — a field no longer looks "broken" the instant the browser considers it invalid, before your app or the user submits anything. The explicit `.sf-is-invalid` / `.sf-is-valid` (and `.sf-is-warning` / `.sf-is-info` / `.sf-is-danger`) state classes are unaffected and remain the way to colour a field's validation state. See `docs/migration.md`. ## [0.7.15] - 2026-07-11 diff --git a/docs/migration.md b/docs/migration.md index 22327a43..5b179bae 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -67,6 +67,32 @@ the size tier on that card: .sf-card--compact .sf-btn { --sf-btn-font-size--size: var(--sf-text-s); } ``` +### Form fields no longer auto-colour on native `:user-invalid` / `:user-valid` (breaking) + +`optional/forms.css` used to flip `--sf-field-border-color` to `--sf-color-danger` +/ `--sf-color-success` automatically whenever the browser's own constraint +validation (`:user-invalid` / `:user-valid`) fired — so a `required` or +`pattern`-constrained field could render "broken" (red border) as soon as it was +touched, before the user submitted anything or your app ran its own validation. +This was the same category of surprise as the card/button rule above — the +framework silently overriding an element's appearance based on browser-internal +state you didn't opt into — so it has been removed. + +**What changed for you:** native constraint validation no longer changes a +field's border colour by itself. The explicit state classes in `core/states.css` +— `.sf-is-invalid` / `.sf-is-error`, `.sf-is-valid` / `.sf-is-success`, +`.sf-is-warning`, `.sf-is-info`, `.sf-is-danger` — are unaffected and remain the +one way to colour a field's validation state; add the class when *your* code +(not the browser) decides the field is invalid: + +```html + +``` + +The underlying `--sf-field-border-color` token and its consumers (resting +border, focus, autofill) are unchanged — only the automatic pseudo-class +trigger is gone. + ### `--sf-touch-target` decoupled from `--sf-size-l`; size scale regularised (breaking) `--sf-touch-target` used to be `var(--sf-size-l)`, which coupled the WCAG 2.5.5 diff --git a/optional/forms.css b/optional/forms.css index 988a1dbc..91d972c0 100644 --- a/optional/forms.css +++ b/optional/forms.css @@ -86,19 +86,6 @@ } /* stylelint-enable selector-pseudo-class-no-unknown */ - /* Native validation states */ - input:user-invalid, - select:user-invalid, - textarea:user-invalid { - --sf-field-border-color: var(--sf-color-danger); - } - - input:user-valid, - select:user-valid, - textarea:user-valid { - --sf-field-border-color: var(--sf-color-success); - } - /* Numeric inputs */ input[type="number"] { font-variant-numeric: var(--sf-font-numeric, tabular-nums); diff --git a/tests/forms.spec.js b/tests/forms.spec.js index a31ca6c0..dfe1cde9 100644 --- a/tests/forms.spec.js +++ b/tests/forms.spec.js @@ -3,8 +3,8 @@ // previously with no dedicated spec (only touched incidentally by a11y.spec). // Asserts the computed styling contract for text inputs, textarea, select, the // disabled state, and the --sf-field-border-color token indirection that the -// :user-invalid / :user-valid states pivot on. Loads the optimal bundle (which -// includes optional/forms.css) in both themes. +// explicit .sf-is-invalid / .sf-is-valid state classes (core/states.css) set. +// Loads the optimal bundle (which includes optional/forms.css) in both themes. import { test, expect } from '@playwright/test'; import { renderWithBundle, NO_TRANSITIONS_STYLE } from './render-helpers.js'; @@ -63,7 +63,7 @@ for (const theme of ['light', 'dark']) { await mount(page, ``); const cs = await computed(page, ['border-top-color']); // The border resolves through --sf-field-border-color (the same hook - // :user-invalid/:user-valid flip to danger/success). + // .sf-is-invalid/.sf-is-valid flip to danger/success — core/states.css). expect(cs['border-top-color']).toBe('rgb(1, 2, 3)'); });