Skip to content

Instantly share code, notes, and snippets.

@esteinborn
Created April 3, 2026 13:26
Show Gist options
  • Select an option

  • Save esteinborn/0154b0972069351d092bad059782b360 to your computer and use it in GitHub Desktop.

Select an option

Save esteinborn/0154b0972069351d092bad059782b360 to your computer and use it in GitHub Desktop.
ElementInternals GAP analysis

Here's the complete ElementInternals gap analysis:


ElementInternals Gap Analysis — NYSDS Components

Current State

12 components use ElementInternals (all form-associated). ~20 presentational components do not (and most don't need to).


Gap 1: Missing Lifecycle Callbacks

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.


Gap 2: ARIA via ElementInternals — Underutilized

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.


Gap 3: Inconsistent Validation Patterns

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)

Gap 4: Toggle Missing Validation

nys-toggle has formAssociated = true and setFormValue() but:

  • No _manageRequire() — can't enforce "must be checked"
  • No setValidity() calls
  • No ariaInvalid management
  • No error message display

Gap 5: nys-datepicker Missing formResetCallback

Unlike all other form controls, nys-datepicker does not implement formResetCallback(). Form resets won't clear the date value.


Gap 6: nys-radiogroup / nys-checkboxgroup Missing ariaInvalid

These group components use setValidity() but never set this._internals.ariaInvalid, unlike all other form controls that do both.


Utility Script Opportunities

Based on the repeated patterns, here are candidates for shared utilities:

1. FormAssociatedMixin (highest value)

Encapsulates the full form-associated lifecycle:

  • static formAssociated = true
  • attachInternals() in constructor
  • formResetCallback() — generic reset (clear value, clear errors, clear validity)
  • formDisabledCallback() — propagate disabled state
  • formStateRestoreCallback() — restore value from state

Would apply to: All 12 form-associated components

2. ValidationMixin / ValidityController

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)

3. FormSubmitController

Handles form submission focus management:

  • Listen for form submit event
  • Focus first invalid element
  • Access this._internals.form

Would apply to: textinput, textarea, select, combobox, fileinput, datepicker (6 components)

4. ARIAReflectionHelper

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-by-Component Summary

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


Priority Ranking

  1. Add formDisabledCallback to all form controls — fieldset disabling is broken without it
  2. Create FormAssociatedMixin — deduplicate ~200+ lines across 12 components
  3. Create ValidationMixin — deduplicate ~150+ lines across 9 components
  4. Fix nys-datepicker missing formResetCallback
  5. Fix nys-checkboxgroup / nys-radiogroup missing ariaInvalid
  6. Add validation to nys-toggle — currently has no required/validity support
  7. Add formStateRestoreCallback — enables browser back/forward and autofill
  8. Expand ARIA via internals — move role/ariaRequired/ariaDisabled to host level
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment