A Separate Mobile Chat Component With One Answer Logic
My mobile chat needed its own messaging-app-style frame, but the answer logic stays single-sourced by reusing the exported TurnBody.
TL;DR
A desktop three-column chat layout doesn't survive shrinking to phones, so the author built a separate mobile thread component using the familiar messaging-app pattern. Sticky header, chat bubbles, composer, all done with CSS sticky and backdrop-filter. Only the frame is duplicated; shared TurnBody logic keeps desktop and mobile in sync.
Reading a long AI answer on a phone usually ends the same way: question and answer melt into one column, the only separator is a thin tint, and I lose track of which part I am reading. On desktop that never happens because three columns divide the roles. My first instinct was to simply shrink those three columns until they fit a phone. That turned out to be the wrong road.
A different structure for a narrow screen
A phone needs a different structure, not a squeezed desktop. In this commit a new component named ChatMobileThread renders only below the lg breakpoint; the three-column desktop stays untouched. Its shape follows the messaging-app pattern everyone already knows: a sticky header with an avatar and a short status, two-sided chat bubbles with the user's question on the right and the assistant's answer on the left complete with an avatar and turn number, then follow-up chips and a composer at the bottom.
Two CSS details carry that feel. The header uses position: sticky, which the MDN documentation describes as flowing with the document and then offset relative to the nearest scrollport, so it sticks while scrolling [8]. The glassy blur comes from backdrop-filter: per MDN, the property applies a graphical effect to the area behind an element and needs a transparent or semi-transparent background to be visible [9].
Only the frame is duplicated
The trap of building a second view component is obvious: two copies of the answer logic. If the fallback banner, markdown rendering, site cards, knowledge sources, error display, and retry were rewritten inside the mobile component, within two sprints you would own two behaviors drifting apart.
The route taken here is different. ChatOutputPanel exports TurnBody, and the mobile component simply consumes it. All answer logic stays single-sourced; only the layout frame around it differs, from the bubbles down to the composer. When the desktop gains a new feature inside TurnBody, the phone inherits it in the same commit.
Exactly how React is taught
This pattern is nearly word for word Thinking in React: break the UI into components, and ideally each component handles one concern [10]. A mobile thread's responsibility is re-staging the conversation for a thumb; TurnBody's responsibility is rendering one turn correctly. Two components, two reasons to change, nothing duplicated.
While judging the source cards for the middle panel, I also wrote about why a citation badge is not the card you see; there too, reuse through a named export became the contract that keeps things consistent.
The size proves the point: that mobile frame is around 271 lines, yet not one of them holds new answer logic. If the design changes completely someday, only the presentation layer gets touched.
Sources
- [8] MDN: position, accessed 2026-09-08
- [9] MDN: backdrop-filter, accessed 2026-09-08
- [10] react.dev: Thinking in React, accessed 2026-09-08