feat(a11y): message semantics for screen readers (P3-4)

- Each message is role="article"; collapsed messages (consecutive from one
  sender) now carry an aria-label with sender + time — previously a screen
  reader heard only the body with no attribution (the biggest a11y gap).
  Pure messageAriaLabel() reuses the existing time utils (+3 tests).
- Editing a message announces "Editing message from <sender>" (ariaLabel
  threaded MessageEditor → CustomEditor; the main composer is unaffected).
- System emoji get role="img" + aria-label from the shortcode; custom
  emoticons always have an accessible name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 11:45:21 -04:00
parent 8ab1ec254b
commit 8729ccfcf5
6 changed files with 90 additions and 11 deletions
+4 -1
View File
@@ -66,6 +66,8 @@ type CustomEditorProps = {
maxHeight?: string;
editor: Editor;
placeholder?: string;
/** Explicit accessible name for the textbox; falls back to the placeholder. */
ariaLabel?: string;
onKeyDown?: KeyboardEventHandler;
onKeyUp?: KeyboardEventHandler;
onChange?: EditorChangeHandler;
@@ -82,6 +84,7 @@ export const CustomEditor = forwardRef<HTMLDivElement, CustomEditorProps>(
maxHeight = '50vh',
editor,
placeholder,
ariaLabel,
onKeyDown,
onKeyUp,
onChange,
@@ -139,7 +142,7 @@ export const CustomEditor = forwardRef<HTMLDivElement, CustomEditorProps>(
data-editable-name={editableName}
className={css.EditorTextarea}
placeholder={placeholder}
aria-label={placeholder ?? 'Message input'}
aria-label={ariaLabel ?? placeholder ?? 'Message input'}
aria-multiline="true"
renderPlaceholder={renderPlaceholder}
renderElement={renderElement}