# Accessibility

A person can do every command of the editor with the keyboard, and the
editor gives the roles and states of its parts to assistive technology.

## The name of the editor

The editable area has `role="textbox"` and `aria-multiline="true"`. Give it
a visible label, and its id as `label_id`:

```heex
<label id="post-body-label">Body</label>
<.kotoba field={@form[:body]} id="post-body" label_id="post-body-label" />
```

The editor then has `aria-labelledby`. Without `label_id`, it has an
`aria-label`: the `label` attribute, or the field name in words ("Body").
The placeholder is also in `aria-placeholder`. Give a description with
`aria-describedby`, and mark a field with an error with `aria-invalid`:
both go on the editable area, and follow each render, so an error's id
can come and go with the error:

```heex
<.kotoba
  field={@form[:body]}
  aria-invalid={@form[:body].errors != []}
  aria-describedby={if @form[:body].errors != [], do: "body-hint body-error", else: "body-hint"}
/>
```

## The toolbar

The toolbar has `role="toolbar"`, with a `group` for each set of buttons
(Text, Blocks, Lists, Insert, Assist, History, and one for each extension,
named after it). An Image group is there only while an image is selected. A Code group is there only while the selection is in a code
block, and a Table group only while it is in a table.

* The toolbar is one tab stop. Tab goes to the toolbar, and Tab again goes
  to the editable area.
* The Left and Right arrows move between the buttons. Home and End go to
  the first and the last button.
* Enter or Space runs the command of the button.
* A format or block button has `aria-pressed`. A button that cannot act
  now (Undo with no history, say) has `aria-disabled="true"`.
* The title of a button shows its shortcut: `Cmd/Ctrl+B` (bold),
  `Cmd/Ctrl+I` (italic), `Cmd/Ctrl+U` (underline), `Cmd/Ctrl+Shift+H`
  (highlight), `Cmd/Ctrl+,` (subscript), `Cmd/Ctrl+.` (superscript),
  `Cmd/Ctrl+K` (link) and `Cmd/Ctrl+Z` (undo).
* An editor with fewer features (see `Kotoba.Features`) has only their
  buttons, and an extension's buttons join the toolbar with the same
  keyboard behaviour, `aria-pressed` and live region messages.
* The Highlight button opens the color palette (`aria-haspopup="dialog"`
  and `aria-expanded`): a dialog named "Color" with a group of text colors,
  a group of highlights and "Remove color". Each color is a button with its
  name ("Blue text", "Green highlight"), and `aria-pressed` on the colors
  of the selection (none for a selection with mixed colors). The focus
  goes to the pressed color, or the first one. The arrow keys, Home and End
  move between the buttons, Enter or Space applies a color to the
  selection and goes back to the editable area, and Escape closes the
  palette, back to its button. The live region says "Blue text", "Green
  highlight" or "Color removed". `Cmd/Ctrl+Shift+H` toggles the default
  (yellow) highlight with no palette.
* Alt+F10 in the editable area moves the focus to the toolbar's tab stop.
  It is the way to the toolbar from a table cell, where Tab and Shift+Tab
  move between the cells.

After a command from the keyboard, the focus goes back to the editable
area, with the same selection. The command acts on the selection, so the
person can go on typing. The toolbar keeps its tab stop on the button of
the last command: Shift+Tab from the editable area goes back to it.

## Tab and Shift+Tab

The editor is not a keyboard trap.

* In a list item, Tab indents the item and Shift+Tab outdents it, where
  the caret is in the item. With a selection over several items, they
  move together. Shift+Tab in an item that is not indented moves the focus
  out of the editor.
* In a code block, Tab inserts a tab character.
* In a table cell, Tab moves to the next cell and Shift+Tab to the
  previous one. From the last cell, Tab moves the caret after the table,
  and from the first cell Shift+Tab moves it before the table.
* Everywhere else, Tab and Shift+Tab move the focus out of the editor, as
  in any form field.
* Escape takes the focus out of the editor. Then Tab goes to the next
  control of the page. Use Escape then Tab to leave a list, a code block
  or a table.

## The code language picker

In a code block, the Code group has a `<select>` named "Code language".
It is part of the toolbar's roving tab stop.

* Left and Right move to the other controls; Up and Down choose a
  language, which applies at once. The focus stays on the picker, so the
  person can try another language.
* The live region says the language ("Code language Python").
* A block in a language that the editor does not know shows it as
  "cobol (not highlighted)", and keeps it.

## Tables

The Table button inserts a table of three rows and three columns, with a
header row, and puts the caret in its first cell. In a table, the Table
group has Insert row above, Insert row below, Insert column before,
Insert column after, Header row, Header column, Delete row, Delete column
and Delete table.

* The commands act on the cell of the caret, and the caret stays in it
  when a row or a column is inserted. The live region says what happened
  ("Row inserted", "Table deleted").
* Header row and Header column have `aria-pressed`: they make the first
  row or the first column header cells.
* When the table is deleted (or the selection leaves it) while a Table
  button has the focus, the focus goes back to the editable area.
* The rendered HTML has `th scope="col"` in the header row and
  `th scope="row"` in the header column, and a first row of header cells
  in a `thead`, so a screen reader reads the header of each cell.

## The prompt menu

When a trigger (for example `@`) opens the menu:

* The menu is a `listbox`, named after the prompt ("people suggestions"),
  or with the prompt's label (see [Prompts](prompts.md)).
  The editable area has `aria-controls` with the menu's id, and
  `aria-activedescendant` with the active option.
* Up and Down move the active option. Enter or Tab inserts it. Escape
  closes the menu.
* Under the options, a status tells what the menu is waiting for:
  "Keep typing to search" (a query shorter than the prompt's
  `min_length`), "Searching…", "No results" or "Results did not load" (a
  search that failed, or that had no answer after 8 seconds). The live
  region says the number of results, "No results" or "Results did not
  load", and then "Inserted Ada Lovelace" (or "Could not insert …" when
  the node of an `insert: {:node, type}` prompt cannot be made).

## The link form

`Cmd/Ctrl+K` or the Link button opens a small form for the URL. It keeps
the selection while the focus is in its input. Enter applies the link, and
Escape closes the form and returns to the editable area.

## Suggestions

A suggestion is text that the server streams (see the
[Suggestions](suggestions.md) guide).

* The Assist button has `aria-haspopup="menu"` and `aria-expanded`. Its
  menu has `role="menu"` and the name "Assist", with the focus on its
  first item. The arrow keys, Home and End move between the items, Enter
  or Space asks for a suggestion and goes back to the editable area, and
  Escape closes the menu, back to its button.
* The suggestion shows in a region named after it ("Rewrite"), with
  `aria-busy` while it streams, a status ("Writing…", "Done", "Stopped")
  and its buttons: Stop, then Accept and Reject.
* In the editable area, `Cmd/Ctrl+Enter` accepts the suggestion, and
  Escape stops it while it streams, or rejects it.
* The live region says "Rewrite: writing", then "Suggestion ready: accept
  it with Ctrl+Enter, or reject it with Escape", and "Suggestion
  inserted", "Suggestion discarded" or "Suggestion stopped". It does not
  read the text as it streams.

## Announcements

A polite live region (`role="status"`) in the editor tells the result of
actions that do not show at the caret: "Bold on", "Link applied", the
number of prompt results, "Inserted Ada Lovelace", and "Uploading
cat.png".

## Uploads

The "Attach a file" button opens the file picker. The LiveView file input
is visually hidden and out of the tab order, because the toolbar button
does the same thing. It has a label for assistive technology
(`upload_label`, "Attach files" by default). The live region tells when an
upload starts.

## Galleries

A gallery is a group (`role="group"`, named "Gallery") of images. The
caret does not go in it; the person selects its images.

* Left and Right select the previous or the next image, and past the
  first or the last one, leave the gallery (as Up and Down do). Right or
  Down at the end of the block before a gallery selects its first image;
  Left or Up at the start of the block after it, its last image.
* The live region says the position of the selected image: "2 of 3:
  cat.png".
* `Alt+Left` and `Alt+Right` move the selected image ("Moved to 3 of 3:
  cat.png", "Already the last image"). Each move is an undo step.
* Backspace and Delete remove the selected image ("Removed cat.png") and
  select the image next to it.
* The toolbar's Image group shows while an image is selected: Gallery
  (`aria-pressed`: the image is in a gallery) groups the image with the
  images next to it ("Gallery of 3 images"), or ungroups its gallery
  ("Gallery ungrouped"); Move image left and Move image right move it, and
  have `aria-disabled` at the ends of the gallery. After one of them, the
  focus stays on its button, so that the person can move the image again.

## Attachments and mentions

Attachments and mentions are single units in the text. Backspace before
the caret, or Delete after it, removes one. An image attachment renders
with its caption as the `alt` text, or its file name when it has none.

The person writes the caption in the field under the selected attachment
(see the Uploads guide): Alt+F10 moves the focus to it from the editable
area, Enter keeps the caption and Escape leaves it as it was; both give the
focus back to the editable area, with the attachment selected. The field
is named after the file ("Caption of timeline.png"), and the live region
says "Caption saved" or "Caption removed". Escape in the editable area
hides the field until the selection changes.

A PDF's viewer and a video's player are named after the caption, else the
file (`aria-label`). In the editor they are out of the tab order, so Tab does
not go into them: the person selects the attachment, and reads or plays
it in the saved page, where the viewer and the player's controls take the
focus. A video never plays by itself. The caption of a PDF or a video is a
download link, for a browser that cannot show it.

## Motion

When the person asks for reduced motion (`prefers-reduced-motion:
reduce`), the editor has no transitions, animations or smooth scrolling.
