diff --git a/CLAUDE.md b/CLAUDE.md index 8ef049d..4228d04 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -335,15 +335,24 @@ because it lives in core rather than in one of the two packages that need it. #### Validation Behavior - **The convention governs browser *constraint validation*, not accessibility annotations** (#199). Those are different things, and conflating them is what left required fields silent to screen - readers. `Required()` still routes validation server-side and the browser still runs none — but a - required field must be *identified*, which is WCAG 2.1 3.3.2 (Level A) -- `Required()` therefore DOES set MudBlazor's `Required` on both render paths, which emits - `aria-required="true"`, the `*` asterisk, and the HTML5 `required` attribute. MudBlazor derives all - three from one flag and they cannot be separated: `MudInput` splats `UserAttributes` and then writes - its own `required`/`aria-required` afterwards, so a caller-supplied value is always overwritten - (measured on 9.8.0). The HTML5 attribute is inert here because the form renders `novalidate`. - ⛔ Do not "restore the convention" by dropping this — that is #190, which #199 reversed, and it - reintroduces a Level A accessibility failure. `.WithNativeRequired(false)` is the per-field opt-out + readers. `Required()` routes validation server-side and the browser runs none — but a required + field must still be *identified*, which is WCAG 2.1 3.3.2 (Level A) +- **`Required()` adds validation but NOT the HTML5 `required` attribute** — the convention in its + literal form, restored by #263. It was amended between #199 and #263 only because MudBlazor 9.8.0 + fused the two: `MudInput` splatted `UserAttributes` and then wrote its own `required` **and** + `aria-required` afterwards, both off one `Required` flag, so last-write-wins meant a caller could + never supply the annotation on its own. [MudBlazor#13613](https://github.com/MudBlazor/MudBlazor/pull/13613) + moved the ARIA writes *above* the splat and left `required` below it; shipped in **9.9.0** +- `Required()` therefore now contributes **`aria-required="true"` through `UserAttributes`** and + leaves MudBlazor's `Required` parameter alone. ⛔ Do not "simplify" this back to setting `Required` + — that reintroduces the HTML5 attribute this convention forbids. And do not drop the annotation + either: that is #190, and it is a Level A failure. Both halves are pinned by `AriaRequiredTests` +- ⚠️ **The visible asterisk is gone for a plain `.Required(...)` field**, and that is a decision, not + a regression to fix. MudBlazor draws it from the same `Required` parameter as the HTML5 attribute, + and #13613 did not separate *those* two. `.WithNativeRequired()` is the documented way back to + native semantics — asterisk and HTML5 attribute together +- 🔒 **Version floor: MudBlazor ≥ 9.9.0.** On anything older, 9 of the 35 `AriaRequiredTests` fail + (measured by reverting the pin). Do not lower `Directory.Packages.props` below it - Browser validation disabled via a `novalidate` attribute **rendered on the form** by `FormCraftComponent` (#206). It is a real attribute in the markup, so it applies during prerender/SSR, targets this component's own form rather than the first one on the page, and needs @@ -352,15 +361,18 @@ because it lives in core rather than in one of the two packages that need it. ran on the server pass, failed silently, and was blocked outright by a strict CSP - All validation through FluentValidation - Validation messages from server, not browser -- MudBlazor components DO set `Required` for a `.Required(...)` field since #199 (see above), on - **every** field type that can carry it: text, numeric, date, select, multi-select, autocomplete, - lookup, LOV and boolean, on both render paths. ⛔ Keep it uniform when adding a field type. Once - required fields carry an asterisk, absence stops meaning "not annotated" and starts meaning - "optional", so a new renderer that skips this actively mis-signals rather than merely omitting -- **Checkboxes take a different route.** `MudCheckBox`/`MudSwitch` emit no `aria-required`, so - FormCraft passes it via `UserAttributes` — which lands there because nothing downstream re-emits - it. Do **not** copy that trick to `MudInput`-based fields: there MudBlazor's own later write always - wins, which is the whole reason `Required` had to be the mechanism (see `EffectiveNativeRequired`) +- MudBlazor components announce `aria-required` for a `.Required(...)` field on **every** field type + that can carry it: text, numeric, date, select, multi-select, autocomplete, lookup, LOV and + boolean. ⛔ Keep it uniform when adding a field type — a required field that says nothing is a + Level A failure, and one that says `aria-required="false"` states the opposite of the truth. The + value comes from `MudBlazorFieldComponentBase.AriaRequired`; bind it alongside `Required` in the + new component's `.razor` +- **Every field type now takes the `UserAttributes` route** that checkboxes always did. Since 9.9.0 + a caller-supplied `aria-required` wins on `MudInput`-based fields too, so there is one mechanism + rather than two. ⚠️ `AriaRequired` returns `"true"`/`"false"` and **never `null`**: a null + component parameter is still captured into `UserAttributes` as a present key, and Blazor deletes + an attribute whose last write is null — returning `null` for optional fields drops + `aria-required` entirely instead of leaving it `"false"` (measured; it turned five assertions red) - **File upload is covered too, but NOT via `Required` on `MudFileUpload`** (#262). Its `` carries `tabindex="-1"` at `opacity-0` behind a custom drop zone, so annotating that input satisfies a DOM assertion while reaching no user who navigates by focus. diff --git a/Directory.Packages.props b/Directory.Packages.props index 40c84a5..cf41018 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,7 +15,7 @@ --> - +