Plushie.Type.A11y (Plushie v0.7.2)

Copy Markdown View Source

Accessibility annotation type for widget nodes.

Every widget declares default a11y values (role, label source) in its widget definition. When the user sets a11y on a widget, the user's values are merged with the defaults (user wins per field). The resolved a11y flows through the tree to the renderer, selectors, assertions, and assistive technology.

Most widgets need no explicit a11y annotation because the defaults provide the correct role and derive the label from the widget's props.

Construction

Builder chain (like Border, Shadow, Font):

A11y.new()
|> A11y.role(:heading)
|> A11y.level(1)
|> A11y.label("Page title")

Bare map via cast/1 (convenience for inline props):

Button.new("btn", "Go", a11y: %{label: "Go forward"})

DSL do-block via from_opts/1:

button "btn", "Go" do
  a11y do
    label "Go forward"
  end
end

Override semantics

Most fields are optional overrides. When nil (the default), the renderer uses its auto-inferred value. When set, the SDK value wins.

Some widgets auto-manage certain fields based on their interaction state. For example, sliders set busy: true during drag so that assistive technology suppresses rapid value announcements and announces only the final value on release. Setting busy explicitly from the SDK overrides this auto-detected state.

Busy and live regions

busy maps to WAI-ARIA aria-busy. When true on a node (or a parent of a live region), assistive technology suppresses announcements until busy clears, then announces the final state.

Widgets that own continuous interactions (sliders) set this automatically. For app-managed live regions that reflect another widget's state (e.g. a text display showing a color value during canvas drag), set busy explicitly based on the interaction state:

text("hex", hex_value,
  a11y: %{live: :polite, busy: model.drag != :none}
)

Fields

  • role - overrides the inferred accesskit role (e.g. :heading, :alert)
  • label - accessible name announced by screen readers
  • description - longer description (maps to accesskit description)
  • live - live region semantics: :polite or :assertive
  • hidden - if true, node is excluded from the accessibility tree
  • expanded - expanded/collapsed state for disclosure widgets
  • required - marks a form field as required
  • level - heading level (1-6)
  • busy - suppresses AT announcements until cleared (auto-managed by sliders during drag; set explicitly for custom continuous interactions)
  • invalid - form validation failure
  • modal - dialog is modal
  • read_only - can be read but not edited
  • mnemonic - Alt+letter keyboard shortcut (single character)
  • toggled - toggled/checked state (for custom toggle widgets)
  • selected - selected state (for custom selectable widgets)
  • value - current value as a string (for custom value-displaying widgets)
  • orientation - :horizontal or :vertical (for custom oriented widgets)
  • labelled_by - ID of the widget that labels this one
  • described_by - ID of the widget that describes this one
  • error_message - ID of the widget showing the error message for this one
  • disabled - override disabled state for AT
  • position_in_set - 1-based position within a set (lists, radio groups, tabs)
  • size_of_set - total items in the set
  • active_descendant - ID of the currently active child in a composite widget
  • radio_group - list of IDs of all radio buttons in a group
  • label_from - (build-time only) prop name to derive label from
  • has_popup - popup type: "listbox", "menu", "dialog", "tree", "grid"

Summary

Functions

Suppresses AT announcements until cleared.

Normalizes a struct, map, or keyword list into an A11y struct.

Sets the ID of the widget that describes this one.

Sets the longer accessible description.

Overrides the disabled state for assistive technology.

Sets the ID of the widget showing the error message.

Sets the expanded/collapsed state.

Constructs an A11y struct from a keyword list.

Sets the popup type ("listbox", "menu", "dialog", "tree", "grid").

Sets whether the node is hidden from the accessibility tree.

Sets the form validation failure state.

Sets the accessible label (name announced by screen readers).

Sets the ID of the widget that labels this one.

Sets the heading level (1-6).

Sets the live region semantics (:polite or :assertive).

Merges widget-default a11y with user-provided overrides.

Sets the Alt+letter keyboard shortcut (single character).

Sets whether a dialog is modal.

Creates an empty A11y struct with all fields nil.

Sets the orientation (:horizontal or :vertical).

Sets the 1-based position within a set.

Sets the read-only state.

Marks a form field as required.

Resolves derived a11y values from the widget's props.

Sets the accessibility role.

Sets the selected state.

Sets the total number of items in the set.

Sets the toggled/checked state.

Sets the current value as a string.

Types

has_popup()

@type has_popup() :: String.t() | nil

live()

@type live() :: :polite | :assertive

orientation()

@type orientation() :: :horizontal | :vertical

role()

@type role() ::
  :window
  | :tree_item
  | :tree
  | :tooltip
  | :toolbar
  | :text_input
  | :column_header
  | :table_cell
  | :table_row
  | :table
  | :tab_panel
  | :tab_list
  | :tab
  | :switch
  | :status
  | :static_text
  | :slider
  | :separator
  | :search
  | :scroll_view
  | :scroll_bar
  | :region
  | :radio_group
  | :radio_button
  | :progress_indicator
  | :navigation
  | :multiline_text_input
  | :meter
  | :menu_item
  | :menu_bar
  | :menu
  | :list_item
  | :list
  | :link
  | :label
  | :image
  | :heading
  | :group
  | :generic_container
  | :document
  | :dialog
  | :combo_box
  | :check_box
  | :canvas
  | :button
  | :alert_dialog
  | :alert

role_input()

@type role_input() ::
  role()
  | :text_editor
  | :row
  | :radio
  | :progress_bar
  | :generic
  | :container
  | :checkbox
  | :cell

t()

@type t() :: %Plushie.Type.A11y{
  active_descendant: String.t() | nil,
  busy: boolean() | nil,
  described_by: String.t() | nil,
  description: String.t() | nil,
  disabled: boolean() | nil,
  error_message: String.t() | nil,
  expanded: boolean() | nil,
  has_popup: has_popup(),
  hidden: boolean() | nil,
  invalid: boolean() | nil,
  label: String.t() | nil,
  label_from: atom() | nil,
  labelled_by: String.t() | nil,
  level: pos_integer() | nil,
  live: live() | nil,
  mnemonic: String.t() | nil,
  modal: boolean() | nil,
  orientation: orientation() | nil,
  position_in_set: non_neg_integer() | nil,
  radio_group: [String.t()] | nil,
  read_only: boolean() | nil,
  required: boolean() | nil,
  role: role() | nil,
  selected: boolean() | nil,
  size_of_set: non_neg_integer() | nil,
  toggled: boolean() | nil,
  value: String.t() | nil
}

Functions

busy(a, busy)

@spec busy(a11y :: t(), busy :: boolean()) :: t()

Suppresses AT announcements until cleared.

Sliders set this automatically during drag. For custom continuous interactions, set explicitly to prevent rapid-fire announcements on live regions.

cast(a11y)

@spec cast(a11y :: t() | map() | keyword()) :: {:ok, t()} | :error

Normalizes a struct, map, or keyword list into an A11y struct.

Accepts an A11y struct, a bare map with atom keys, or a keyword list. Unknown keys are silently ignored. Role aliases like :checkbox and :radio are normalized to their canonical forms.

Examples

iex> Plushie.Type.A11y.cast(%{role: :heading, level: 1})
{:ok, %Plushie.Type.A11y{role: :heading, level: 1}}

iex> Plushie.Type.A11y.cast(role: :heading, level: 1)
{:ok, %Plushie.Type.A11y{role: :heading, level: 1}}

iex> a11y = %Plushie.Type.A11y{label: "Close"}
iex> Plushie.Type.A11y.cast(a11y)
{:ok, %Plushie.Type.A11y{label: "Close"}}

described_by(a, id)

@spec described_by(a11y :: t(), id :: String.t()) :: t()

Sets the ID of the widget that describes this one.

description(a, desc)

@spec description(a11y :: t(), description :: String.t()) :: t()

Sets the longer accessible description.

disabled(a, disabled)

@spec disabled(a11y :: t(), disabled :: boolean()) :: t()

Overrides the disabled state for assistive technology.

error_message(a, id)

@spec error_message(a11y :: t(), id :: String.t() | nil) :: t()

Sets the ID of the widget showing the error message.

expanded(a, expanded)

@spec expanded(a11y :: t(), expanded :: boolean()) :: t()

Sets the expanded/collapsed state.

from_opts(opts)

@spec from_opts(opts :: keyword()) :: t()

Constructs an A11y struct from a keyword list.

has_popup(a, popup)

@spec has_popup(a11y :: t(), popup :: String.t()) :: t()

Sets the popup type ("listbox", "menu", "dialog", "tree", "grid").

hidden(a, hidden)

@spec hidden(a11y :: t(), hidden :: boolean()) :: t()

Sets whether the node is hidden from the accessibility tree.

invalid(a, invalid)

@spec invalid(a11y :: t(), invalid :: boolean()) :: t()

Sets the form validation failure state.

label(a, label)

@spec label(a11y :: t(), label :: String.t()) :: t()

Sets the accessible label (name announced by screen readers).

labelled_by(a, id)

@spec labelled_by(a11y :: t(), id :: String.t()) :: t()

Sets the ID of the widget that labels this one.

level(a, level)

@spec level(a11y :: t(), level :: pos_integer()) :: t()

Sets the heading level (1-6).

live(a, live)

@spec live(a11y :: t(), live :: live()) :: t()

Sets the live region semantics (:polite or :assertive).

merge(default, override)

Merges widget-default a11y with user-provided overrides.

Non-nil fields in the override replace the corresponding default. Nil fields in the override keep the default value. This allows widgets to declare default roles and labels while letting users override specific fields.

mnemonic(a, char)

@spec mnemonic(a11y :: t(), mnemonic :: String.t()) :: t()

Sets the Alt+letter keyboard shortcut (single character).

modal(a, modal)

@spec modal(a11y :: t(), modal :: boolean()) :: t()

Sets whether a dialog is modal.

new()

@spec new() :: t()

Creates an empty A11y struct with all fields nil.

orientation(a, orientation)

@spec orientation(a11y :: t(), orientation :: orientation()) :: t()

Sets the orientation (:horizontal or :vertical).

position_in_set(a, pos)

@spec position_in_set(a11y :: t(), pos :: non_neg_integer()) :: t()

Sets the 1-based position within a set.

read_only(a, read_only)

@spec read_only(a11y :: t(), read_only :: boolean()) :: t()

Sets the read-only state.

required(a, required)

@spec required(a11y :: t(), required :: boolean()) :: t()

Marks a form field as required.

resolve(a11y, props)

Resolves derived a11y values from the widget's props.

If label_from is set and label is nil, copies the value of the named prop as the accessible label. The label_from field is then cleared (it's a build-time directive, not a wire prop).

role(a, role)

@spec role(a11y :: t(), role :: role_input()) :: t()

Sets the accessibility role.

Supported aliases normalize to canonical roles: :checkbox -> :check_box, :radio -> :radio_button, :text_editor -> :multiline_text_input, :progress_bar -> :progress_indicator, :generic / :container -> :generic_container, :row -> :table_row, and :cell -> :table_cell.

selected(a, selected)

@spec selected(a11y :: t(), selected :: boolean()) :: t()

Sets the selected state.

size_of_set(a, size)

@spec size_of_set(a11y :: t(), size :: non_neg_integer()) :: t()

Sets the total number of items in the set.

toggled(a, toggled)

@spec toggled(a11y :: t(), toggled :: boolean()) :: t()

Sets the toggled/checked state.

value(a, value)

@spec value(a11y :: t(), value :: String.t()) :: t()

Sets the current value as a string.