Search documentation of Plushie

# 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

### Types

has_popup()
live()
orientation()
role()
role_input()
t()

### Functions

busy(a, busy)

Suppresses AT announcements until cleared.

cast(a11y)

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

described_by(a, id)

Sets the ID of the widget that describes this one.

description(a, desc)

Sets the longer accessible description.

disabled(a, disabled)

Overrides the disabled state for assistive technology.

error_message(a, id)

Sets the ID of the widget showing the error message.

expanded(a, expanded)

Sets the expanded/collapsed state.

from_opts(opts)

Constructs an `A11y` struct from a keyword list.

has_popup(a, popup)

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

hidden(a, hidden)

Sets whether the node is hidden from the accessibility tree.

invalid(a, invalid)

Sets the form validation failure state.

label(a, label)

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

labelled_by(a, id)

Sets the ID of the widget that labels this one.

level(a, level)

Sets the heading level (1-6).

live(a, live)

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

merge(default, override)

Merges widget-default a11y with user-provided overrides.

mnemonic(a, char)

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

modal(a, modal)

Sets whether a dialog is modal.

new()

Creates an empty A11y struct with all fields nil.

orientation(a, orientation)

Sets the orientation (`:horizontal` or `:vertical`).

position_in_set(a, pos)

Sets the 1-based position within a set.

read_only(a, read_only)

Sets the read-only state.

required(a, required)

Marks a form field as required.

resolve(a11y, props)

Resolves derived a11y values from the widget's props.

role(a, role)

Sets the accessibility role.

selected(a, selected)

Sets the selected state.

size_of_set(a, size)

Sets the total number of items in the set.

toggled(a, toggled)

Sets the toggled/checked state.

value(a, value)

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.