Behaviour a Component implements to describe itself to assistive technology.
Parallels Raxol.MCP.ToolProvider: the callback receives the Component's
declaration Element (the same map mcp_tools/1 receives) and returns an
accessibility node. Raxol.Core.Accessibility.Projection walks the Element
tree and calls this callback for Components that implement it, falling back to
a default role/label extraction otherwise.
A Component knows where its own fields live (field locations are heterogeneous across the codebase), so co-locating the extractor here keeps each Component authoritative about its own shape.
Returning children
Omit :children for leaf Components (Button, TextInput, Checkbox) -- the
projection recurses the Element's own children. Set :children only when the
Component synthesizes semantic children not present as Element children
(e.g. SelectList options built from an :options attr, Tabs, Table rows).
Example
@impl Raxol.Core.Accessibility.Provider
def a11y_node(node) do
%{role: :checkbox, label: node[:label], id: node[:id],
state: %{checked?: node[:checked] || false}}
end
Summary
Types
What a Provider returns: a partial node. Every key is optional.
Callbacks
Builds an accessibility node from a Component's declaration Element.
Functions
Returns true when module implements this behaviour.
Types
@type provided_node() :: %{ optional(:role) => atom(), optional(:label) => term(), optional(:state) => %{optional(atom()) => term()}, optional(:value) => term(), optional(:live?) => boolean(), optional(:children) => [provided_node()], optional(:id) => term() }
What a Provider returns: a partial node. Every key is optional.
Projection.finalize/3 reads each field with a default (role ->
Roles.default_role/0, state -> %{}, live? -> Roles.live?/1, label
and value -> nil) and supplies :id itself from the Element, so a
Provider supplies only what it knows.
This is deliberately NOT Projection.accessibility_node(). That type is the
normalized node -- all seven keys present -- and declaring it as the
callback's return made all 16 conforming Components a dialyzer
callback_type_mismatch, because none of them returns all seven.
label, value, id and the state values are term() rather than
narrower types on purpose: Providers read their fields out of a declaration
Element with node[:key] / attrs[:key], which is term(), and pass the
result straight through (e.g. label: node[:aria_label] || node[:label],
state: %{variant: attrs[:role]}). Projection.normalize_label/1 and
compact_state/1 are what actually constrain them downstream.
Callbacks
@callback a11y_node(element :: map()) :: provided_node()
Builds an accessibility node from a Component's declaration Element.
Returned maps are normalized by Raxol.Core.Accessibility.Projection:
missing keys are filled (role -> :generic, state -> %{}, live?
derived from the role), so a Provider only needs to supply what it knows.
Functions
Returns true when module implements this behaviour.
Guarded with Code.ensure_loaded?/1 so raxol_core can dispatch to Component
modules that live in main raxol (loaded only when main is present) without a
compile-time dependency.