Collapsed long messages: Tab into a hidden link scrolls the message inside its box (top cut off, stays that way), "Read more" has no aria-expanded, and the fade leaves a band in bubble layout #343

Closed
opened 2026-10-10 22:15:44 -04:00 by jared · 0 comments
Owner

CollapsibleBody (src/app/components/message/MsgTypeRenderers.tsx:43-140, from 657ca3a5 "collapsible messages") caps a message at maxHeight: 320px; overflow: hidden until Read more ↓ is clicked. The hidden part stays in the DOM and stays focusable.

1. Keyboard focus scrolls the collapsed box. Measured with a 41-line message whose last line has a link, collapsed to 320 px:

  • Tab to the link (.focus()): overflow: hidden boxes still scroll programmatically, so the browser scrolls the message's own box to scrollTop: 582 to reveal it;
  • the message now shows lines 27–41 with line 27 sliced in half at the top, and the fade is gone (screenshot);
  • the button still says "Read more ↓";
  • after focus leaves, scrollTop stays 582, so the message is wrongly scrolled until a remount.

Fix: hide the overflow from the tab order while collapsed (inert on an inner wrapper of the hidden part, or expand on focusin). Simplest: onFocus={() => setCollapsed(false)} on the body when the target is outside the visible area, plus el.scrollTop = 0 whenever it collapses.

2. The toggle isn't announced as one. The <button> has no aria-expanded or aria-controls. Its name is "Read more ↓" / "Show less ↑", so the arrows are read aloud ("down arrow"). Fix: aria-expanded={!collapsed}, aria-controls pointing at the body, and put the arrows in an aria-hidden span.

3. The fade colour is fixed. The gradient fades to color.Surface.Container, not to what's behind the message. In bubble layout the bubble is lighter (rgb(51,51,51) vs the fade's surface), so a darker band shows across the bottom of the bubble (screenshot). Over the translucent/seasonal backgrounds and on highlighted (mentioned or hovered) messages the band shows too. Fix: fade the content itself with mask-image: linear-gradient(#000 calc(100% - 3rem), transparent) on the collapsed body; it needs no colour and works on any background.

4. Minor. COLLAPSE_MAX_HEIGHT = 320 is px, so with a larger font setting fewer lines show before the cut (the comment's "≈ 20 lines" assumes 16 px). Use 20rem / toRem(320).

Tab to the hidden link: the collapsed message scrolled inside its box (top cut off)

collapse-focus-crop.png

Bubble layout: the fade leaves a band

collapse-bubble.png


Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny 30e2dbcd, cinny-desktop 8f67ec7, element-call 3b0050e8.

`CollapsibleBody` (src/app/components/message/MsgTypeRenderers.tsx:43-140, from 657ca3a5 "collapsible messages") caps a message at `maxHeight: 320px; overflow: hidden` until **Read more ↓** is clicked. The hidden part stays in the DOM and stays focusable. **1. Keyboard focus scrolls the collapsed box.** Measured with a 41-line message whose last line has a link, collapsed to 320 px: - Tab to the link (`.focus()`): `overflow: hidden` boxes still scroll programmatically, so the browser scrolls the **message's own box** to `scrollTop: 582` to reveal it; - the message now shows lines 27–41 with line 27 sliced in half at the top, and the fade is gone (screenshot); - the button still says "Read more ↓"; - after focus leaves, `scrollTop` **stays 582**, so the message is wrongly scrolled until a remount. Fix: hide the overflow from the tab order while collapsed (`inert` on an inner wrapper of the hidden part, or expand on `focusin`). Simplest: `onFocus={() => setCollapsed(false)}` on the body when the target is outside the visible area, plus `el.scrollTop = 0` whenever it collapses. **2. The toggle isn't announced as one.** The `<button>` has no `aria-expanded` or `aria-controls`. Its name is "Read more ↓" / "Show less ↑", so the arrows are read aloud ("down arrow"). Fix: `aria-expanded={!collapsed}`, `aria-controls` pointing at the body, and put the arrows in an `aria-hidden` span. **3. The fade colour is fixed.** The gradient fades to `color.Surface.Container`, not to what's behind the message. In **bubble** layout the bubble is lighter (`rgb(51,51,51)` vs the fade's surface), so a darker band shows across the bottom of the bubble (screenshot). Over the translucent/seasonal backgrounds and on highlighted (mentioned or hovered) messages the band shows too. Fix: fade the content itself with `mask-image: linear-gradient(#000 calc(100% - 3rem), transparent)` on the collapsed body; it needs no colour and works on any background. **4. Minor.** `COLLAPSE_MAX_HEIGHT = 320` is px, so with a larger font setting fewer lines show before the cut (the comment's "≈ 20 lines" assumes 16 px). Use `20rem` / `toRem(320)`. **Tab to the hidden link: the collapsed message scrolled inside its box (top cut off)** ![collapse-focus-crop.png](https://code.lotusguild.org/attachments/ecfb9df8-a2cf-4950-8bff-2a94d120debe) **Bubble layout: the fade leaves a band** ![collapse-bubble.png](https://code.lotusguild.org/attachments/819ab37f-5f38-49bb-828a-78c8b0dc2aa1) --- _Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny `30e2dbcd`, cinny-desktop `8f67ec7`, element-call `3b0050e8`._
jared added the priority: lowa11yarea: messaging labels 2026-10-10 22:15:44 -04:00
jared closed this issue 2026-10-12 16:44:00 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#343