Accessibility

← Documentation
STRVCT

Automatic, framework-level ARIA support, keyboard navigation, and preference adaptation derived from slot metadata annotations and view classes.

Overview

STRVCT provides built-in accessibility support that works automatically for every application built on the framework. Because the UI is derived from model nodes through a small set of view classes, accessibility metadata is emitted at the framework level -- developers don't need to add ARIA attributes to individual components.

This is a direct consequence of the naked objects architecture. In a conventional web framework, accessibility is a per-component concern: every button, list, and form field needs manual ARIA attributes, and the cost scales linearly ($O(N)$) with the number of components. In STRVCT, the same $\sim$15 view classes render every application's UI, so fixing accessibility in those classes fixes it everywhere.

Slot Metadata Annotations Declared once in initPrototypeSlots() slot.description() slot.isReadOnly() slot.isRequired() slot.minimum() / slot.maximum() Framework ARIA Emitted Emitted by ~15 shared view classes aria-description="Current character hit points" aria-readonly="true" aria-required="true" aria-valuemin="0" · aria-valuemax="999" One declaration drives field validation, AI JSON tool schemas, and screen reader ARIA attributes.

Why the Naked Objects Pattern Helps

Most frameworks require developers to manually add accessibility attributes to each component. STRVCT's architecture inverts this: since the framework generates views from the model, it can also generate accessibility metadata from the model -- automatically, in one place, for all applications.

  • The slot system already describes the data: Slot metadata maps almost directly to ARIA attributes (description() $\rightarrow$ aria-description, isReadOnly() $\rightarrow$ aria-readonly, isRequired() $\rightarrow$ aria-required).
  • JSON Schema annotations serve double duty: The same annotations that generate JSON Schema for AI tool calls also drive accessibility attributes. A slot configured with setMinimum(0) and setMaximum(100) emits both JSON Schema constraints for AI and aria-valuemin="0" / aria-valuemax="100" for screen readers. The investment in richer slot annotations directly improves both AI integration and accessibility.
  • The node hierarchy maps to ARIA patterns: The tile/stack navigation model (drill in, drill out, select with arrows) maps naturally to ARIA roles (SvTilesView $\rightarrow$ list, SvTile $\rightarrow$ link, SvFieldTile $\rightarrow$ group, selected tile $\rightarrow$ aria-current).

Implementation Details

ARIA Roles

Each core view class emits an appropriate ARIA role:

View ClassRolePurpose
SvTilesViewlistContainer for navigable tile items
SvTilelinkIndividual navigation item in a list
SvFieldTilegroupKey-value field with label and value
SvBooleanFieldTilecheckboxToggle field with checked/unchecked state
SvActionFieldTilebuttonClickable action
SvOptionsNodeTilelistboxContainer for selectable options
SvOptionNodeTileoptionIndividual selectable option
SvStackViewnavigationTop-level navigation landmark
SvNavViewregionNamed content region
SvBreadCrumbsTilenavigationBreadcrumb navigation with aria-label="Breadcrumb"

Labels and Descriptions

View classes derive ARIA labels from node metadata:

SourceARIA UsageRationale
node.nodeAriaLabel()aria-labeltitle() by default; domain objects can override to provide a descriptive label for screen readers
node.subtitle()aria-descriptionSubtitle text available on demand
slot.description()aria-descriptionDetailed field-level description
Key view textaria-labelLabel on field tiles

Node-Level ARIA Overrides (`SvTitledNode`)

SvTitledNode provides methods that domain objects can override to customize accessibility behavior without modifying view code:

MethodDefaultPurpose
nodeAriaLabel()title()Label announced by screen readers
nodeAriaRole()null (view's default)Override the view class's default ARIA role
nodeAriaIsReadOnly()null (from slot editability)Override read-only state
nodeAriaIsRequired()null (from slot isRequired())Override required state
nodeAriaLive()nullReturns "polite" or "assertive" for dynamic live regions

Returning null defers to the view's default behavior. Returning a value overrides it. This follows the same pattern as setNodeViewClass() -- the framework provides sensible defaults, and the domain object overrides when it knows better.

State Tracking

ARIA states are updated dynamically through the framework's notification system:

  • aria-current -- Set to "true" on the selected tile, removed when deselected. The selected tile also receives focus.
  • aria-checked -- Updated on SvBooleanFieldTile when the boolean value changes.
  • aria-selected -- Updated on SvOptionNodeTile based on isPicked() state.
  • aria-disabled -- Set on SvActionFieldTile when the action's node is not enabled.
  • aria-readonly -- Set on SvFieldTile when the field is not editable.

Slot Metadata to ARIA Mapping (`syncAriaFromSlotMetadata`)

SvFieldTile includes a syncAriaFromSlotMetadata() method that reads slot annotations and emits corresponding ARIA attributes:

{
    const slot = this.newSlot("hitPoints", 0);
    slot.setSlotType("Number");
    slot.setIsRequired(true);
    slot.setAnnotation("minimum", 0);
    slot.setAnnotation("maximum", 999);
    slot.setDescription("Current character hit points");
}

Produces a field tile with aria-required="true", aria-valuemin="0", aria-valuemax="999", and aria-description="Current character hit points" -- with no accessibility-specific code from the developer.

Live Regions

  • Opt-in via nodes: Any node can declare itself as a live region by implementing nodeAriaLive() returning "polite" or "assertive". When SvTilesView syncs from a node with this method, it sets aria-live on the container.
  • Direct on chat: SvChatMessageTile sets aria-live="polite" directly, so new chat messages and AI responses are announced to screen readers as they arrive.

Focus Management & Keyboard Navigation

  • Tiles are programmatically focusable via tabindex="-1". This makes them reachable by the framework's focus management without adding them to the Tab order.
  • Focus indicators use :focus-visible styling (2px solid cornflower blue outline) that appears only for keyboard navigation, not mouse clicks.
  • High contrast mode (prefers-contrast: more) increases the focus outline to 3px solid white for maximum visibility.
  • Focus follows selection -- when a tile is selected, it receives both aria-current="true" and DOM focus, ensuring screen readers announce the newly selected item.

Visual Accessibility & Motion Preferences

  • Reduced Motion (prefers-reduced-motion: reduce): All animations and transitions are reduced to 0.01ms duration, iteration counts set to 1, and scroll behavior set to auto (instant).
  • High Contrast (prefers-contrast: more): Focus indicators enhanced to 3px solid white with tighter offset.

ARIA Landmarks

Landmark roles provide structural navigation for screen readers:

  • SvStackView is marked as navigation -- the top-level navigation structure
  • SvNavView is marked as region with an aria-label derived from the current node's title
  • SvBreadCrumbsTile is marked as navigation with aria-label="Breadcrumb"

WCAG 2.2 Compliance Matrix

WCAG CriterionHow Addressed
1.3.1 Info & RelationshipsARIA roles and landmarks convey structure
1.3.2 Meaningful SequenceDOM order matches visual order (flexbox layout)
2.1.1 KeyboardArrow keys, Enter, Escape for full navigation
2.3.3 Animation from Interactionsprefers-reduced-motion media query
2.4.1 Bypass BlocksLandmark roles enable skip navigation
2.4.6 Headings & Labelsaria-label from node title()
2.4.7 Focus Visible:focus-visible outline styling
3.2.1 On FocusNo context changes on focus
4.1.2 Name, Role, ValueARIA roles, labels, and states on all interactive elements

Files Modifying Accessibility

FileResponsibilities
_css.css:focus-visible styling, prefers-reduced-motion, prefers-contrast
SvTilesView.jsrole="list", aria-label, opt-in aria-live
SvTile.jsrole="link", tabindex="-1", aria-label, aria-description, aria-current
SvFieldTile.jsrole="group", aria-label, aria-readonly, syncAriaFromSlotMetadata()
SvBooleanFieldTile.jsrole="checkbox", aria-checked
SvActionFieldTile.jsrole="button", aria-label, aria-disabled
SvOptionsNodeTile.jsrole="listbox"
SvOptionNodeTile.jsrole="option", aria-selected
SvChatMessageTile.jsaria-live="polite"
SvStackView.jsrole="navigation"
SvNavView.jsrole="region", aria-label
SvBreadCrumbsTile.jsrole="navigation", aria-label="Breadcrumb", aria-current="location"
  • Nodes: Core SvNode domain abstraction.
  • Slots: Complete guide to slot metadata and declarations.