noteloom

Accessibility & RTL

Two related concerns — reading direction and assistive-technology support — that noteloom treats as part of the editor, not an afterthought bolted on later.

Right-to-left & multi-language text

Every block defaults to dir="auto" — the browser's own Unicode bidi algorithm detects direction per block from its first strong character, so a document mixing LTR and RTL blocks (an English heading over an Arabic paragraph, say) just works with zero configuration.

For the cases auto can't infer on its own (most commonly an empty block, which has no text yet to detect a direction from), set an explicit override with the same updateBlockProps primitive used everywhere else:

js
import { operations } from 'noteloom';

// Document-wide default:
store.applyOperation(operations.updateBlockProps(store.getRootId(), { dir: 'rtl' }));

// Or just one block:
store.applyOperation(operations.updateBlockProps(blockId, { dir: 'rtl' }));

A block's own dir wins over the document's default. The per-block gutter menu also has a "Switch to right-to-left"/"left-to-right" item that sets this directly, so an end user doesn't need code access to flip one block's direction.

Code blocks are always dir="ltr" regardless of the surrounding document's default — code syntax (brackets, operators) is structurally LTR no matter what language a comment or string literal happens to be written in.

This covers the reading/typing/gutter-position direction itself; a full logical-properties audit (margin-inline-start etc.) of every pixel value in the default theme is a larger follow-up — the highest-impact pieces (list/checkbox marker position, blockquote border side, block gutter position) already flip correctly.

Keyboard-operable menus

Every portaled popover that's a genuine standalone action menu — the block gutter's Duplicate/Move/Hide/Delete menu, the block-range action menu, a table column's options menu — is fully keyboard-operable: opening one moves real focus onto its first item, / move between items (wrapping), Home/End jump to the first/last, and Escape closes it and returns focus to whatever opened it. Not a name-only role="menu" that only responds to a mouse.

Modal (used by the field-type editor, the document export dialog, and any custom modal you build) moves focus into the dialog on open and restores it to whatever had focus before on close — not a full focus trap, just "focus doesn't go missing."

Live-region announcements

Structural actions that don't otherwise move focus anywhere describable — duplicate/move/hide/delete a block, or a whole selected range — announce what happened via a shared, visually-hidden aria-live="polite" region. A screen-reader user hears "Block deleted" or "3 blocks moved up" instead of silence.

Media & tables

  • Embed images have a real, separately-authored alt text field (a toolbar button opens a small dialog to set it) — alt is never silently filled in from an uploaded file's raw filename or a pasted URL string, since neither is meaningful alt text.
  • Table header cells have scope="col", and both the column-resize and embed-resize sliders expose aria-valuemin/aria-valuemax/aria-valuenow.

Known gaps

  • No accessibility affordance exists for grouping sibling list items under a shared role="list" container (each list item is an independent block, not wrapped in one) — adding role="listitem" without that ancestor would be worse than no role at all, so it's deliberately left out pending a bigger structural change.
  • The library doesn't render your editor's own root/surface element — that's host-rendered — so it can't add role="document"/aria-label there itself. Add those to your own surface element, the way examples/basic in the package repo does.
See Mobile & voice typing for the touch-first and dictation side of accessible input, or the Keyboard shortcuts reference for every built-in shortcut.