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:
<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:
<.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) hasaria-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) andCmd/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-pressedand live region messages. - The Highlight button opens the color palette (
aria-haspopup="dialog"andaria-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"), andaria-pressedon 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+Htoggles 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 andth scope="row"in the header column, and a first row of header cells in athead, 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). The editable area hasaria-controlswith the menu's id, andaria-activedescendantwith 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 aninsert: {: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 guide).
- The Assist button has
aria-haspopup="menu"andaria-expanded. Its menu hasrole="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-busywhile it streams, a status ("Writing…", "Done", "Stopped") and its buttons: Stop, then Accept and Reject. - In the editable area,
Cmd/Ctrl+Enteraccepts 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+LeftandAlt+Rightmove 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 havearia-disabledat 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.