Here's the complete ElementInternals gap analysis:
12 components use ElementInternals (all form-associated). ~20 presentational components do not (and most don't need to).
No component implements these callbacks:
| Callback | Purpose | Components That Should Have It |
|---|---|---|
formDisabledCallback() |
React when form/fieldset is disabled | All 12 form-associated components |
formStateRestoreCallback() |
Restore state after browser back/forward or autofill | All input-type components (textinput, textarea, select, combobox, datepicker, checkbox, toggle, radiogroup, fileinput) |
formAssociatedCallback() |
React when associated with a <form> |
Low priority — useful for lazy init |
Impact: Without formDisabledCallback, disabling a parent <fieldset> won't propagate to NYSDS components. Without formStateRestoreCallback, browser navigation/autofill won't restore form state.
Only ariaInvalid is set through ElementInternals. All other ARIA states are set via template attributes on inner shadow DOM elements. Components that could benefit from host-level ARIA via internals:
| Component | Currently Missing | Recommended |
|---|---|---|
| nys-button | — | internals.role = "button" (for <a> variant) |
| nys-checkbox | — | internals.ariaChecked, internals.ariaRequired, internals.ariaDisabled |
| nys-toggle | — | internals.role = "switch", internals.ariaChecked |
| nys-combobox | — | internals.role = "combobox", internals.ariaExpanded |
| nys-select | — | internals.ariaRequired, internals.ariaDisabled |
| nys-textinput | — | internals.ariaRequired, internals.ariaDisabled |
| nys-textarea | — | internals.ariaRequired, internals.ariaDisabled |
| nys-datepicker | — | internals.ariaRequired, internals.ariaDisabled |
| nys-fileinput | — | internals.ariaRequired, internals.ariaDisabled |
| nys-accordion | Not form-associated | internals.role = "region" on item |
| nys-modal | Not form-associated | internals.role = "dialog", internals.ariaModal |
Why it matters: Setting ARIA on the host via ElementInternals makes components transparent to the accessibility tree without requiring consumers to pierce shadow DOM. It also prevents ARIA attribute conflicts with user-set attributes.
Each component implements validation independently with duplicated logic:
| Pattern | Components | Lines of Duplicated Code |
|---|---|---|
_manageRequire() — required field check |
textinput, textarea, select, combobox, checkbox, fileinput, datepicker | ~15 lines each (7 components) |
_setValidityMessage() — custom error |
textinput, textarea, select, combobox, checkbox | ~10 lines each (5 components) |
formResetCallback() — reset state |
textinput, textarea, select, combobox, checkbox, checkboxgroup, radiogroup, fileinput, toggle | ~8 lines each (9 components) |
_handleFormInvalid() — form invalid event |
textinput, textarea, select, combobox, checkbox, checkboxgroup, radiogroup, fileinput, datepicker | ~5 lines each (9 components) |
_handleFormSubmit() — form submit focus |
textinput, textarea, select, combobox, fileinput, datepicker | ~10 lines each (6 components) |
nys-toggle has formAssociated = true and setFormValue() but:
- No
_manageRequire()— can't enforce "must be checked" - No
setValidity()calls - No
ariaInvalidmanagement - No error message display
Unlike all other form controls, nys-datepicker does not implement formResetCallback(). Form resets won't clear the date value.
These group components use setValidity() but never set this._internals.ariaInvalid, unlike all other form controls that do both.
Based on the repeated patterns, here are candidates for shared utilities:
Encapsulates the full form-associated lifecycle:
static formAssociated = trueattachInternals()in constructorformResetCallback()— generic reset (clear value, clear errors, clear validity)formDisabledCallback()— propagate disabled stateformStateRestoreCallback()— restore value from state
Would apply to: All 12 form-associated components
Standardizes validation logic:
_manageRequire()— check required + value empty →setValidity({ valueMissing })+ariaInvalid_setValidityMessage(message)— custom error →setValidity({ customError })_clearValidity()— reset all validity state_handleFormInvalid()— show error + focus on invalid event
Would apply to: textinput, textarea, select, combobox, checkbox, checkboxgroup, radiogroup, fileinput, datepicker (9 components)
Handles form submission focus management:
- Listen for form
submitevent - Focus first invalid element
- Access
this._internals.form
Would apply to: textinput, textarea, select, combobox, fileinput, datepicker (6 components)
Syncs component properties to ElementInternals ARIA:
syncAriaState({ required, disabled, checked, expanded, invalid })- Handles string coercion (
"true"/"false") - Called from
updated()lifecycle
Would apply to: All interactive components using ElementInternals
| Component | formAssociated | setFormValue | setValidity | ariaInvalid | formResetCB | formDisabledCB | formStateRestoreCB | ARIA via internals |
|---|---|---|---|---|---|---|---|---|
| nys-textinput | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ariaInvalid only |
| nys-textarea | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ariaInvalid only |
| nys-select | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ariaInvalid only |
| nys-combobox | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ariaInvalid only |
| nys-checkbox | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ariaInvalid only |
| nys-checkboxgroup | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | none |
| nys-radiogroup | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | none |
| nys-fileinput | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ariaInvalid only |
| nys-datepicker | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ariaInvalid only |
| nys-toggle | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | none |
| nys-button | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | none |
| nys-errormessage | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | none |
Legend: ✅ = implemented, ❌ = missing
- Add
formDisabledCallbackto all form controls — fieldset disabling is broken without it - Create FormAssociatedMixin — deduplicate ~200+ lines across 12 components
- Create ValidationMixin — deduplicate ~150+ lines across 9 components
- Fix nys-datepicker missing
formResetCallback - Fix nys-checkboxgroup / nys-radiogroup missing
ariaInvalid - Add validation to nys-toggle — currently has no required/validity support
- Add
formStateRestoreCallback— enables browser back/forward and autofill - Expand ARIA via internals — move role/ariaRequired/ariaDisabled to host level