Patterns
How primitives compose
Replicas, not the shipped components โ the real sidebar, message row, composer, thread summary and huddle card call Convex hooks and need a backend to mount. The avatars, buttons and separators inside are the real primitives, and the frame and list colours are each theme's own class strings from shared/themes.ts.
Sidebar
The rail sits directly on the themed frame (theme.sidebar); the channel list floats on it as a rounded-xl panel (theme.workspaceSidebar) with an inset white/6% ring and a 54px header. Rows use the app's own SIDEBAR_ROW_CLASS and sidebarItemClass: read rows dim to brightness .85, unread go white at 900, active take the theme's sidebarItem.active. Channels signal unread by weight alone; DMs add a count pill in --theme-badge with #16090e text. A live huddle pins to the bottom.
Message row
38px avatars and 900 sender names under a date pill. Own reactions take a 12% accent fill, a 1px inset accent ring and an accent count; others are muted. @mentions are accent chips, the open thread's parent holds an accent tint with a 3px bar, and the accent NEW divider marks the first unread. The second row is forced into its hover state.
Pushed the new onboarding flow to staging. I'd love eyes on the invite step before Thursday.
Looking now. The invite step reads well, but the Skip link is easy to miss on phones.
Agree on Skip. I can make it a secondary button, same width as Continue.
JWMC3 repliesLast reply 10:26 AMHuddle at 11 to walk through it together? @Maya @Priya
Composer
A rounded-xl bordered box whose focus cue is a border shift (focus-within:border-foreground/25), not a ring. On top, the full formatting bar: B I U S | link, numbered, bulleted | quote, code, code block | clear. Then the field, then + / emoji / @ / slash / Aa and an accent send joined to the schedule caret (channel, DM and thread composers pass one). Empty, both go muted and the Shift + Return hint hides. Accent fills take white text in light and #16090e in dark, where the accents are pastels. Aa here is a lucide stand-in for the app's react-icons glyph.
md and up ยท typing
Shift + Return to add a new line
md and up ยท empty
Shift + Return to add a new line
Under md, closed is one row: +, field, emoji, Aa, send, all 44px targets except the 32px icons. @ and slash hide. An empty send goes transparent and the disabled caret is dropped. Aa opens formatting, which stacks: a single-row toolbar that scrolls sideways (36px buttons over a hairline), the full-width field, then the action row with send pushed right.
< md ยท closed, empty
< md ยท closed, typing
< md ยท formatting open
Hover toolbar
A floating card pinned 16px above the row's top-right edge: two quick reactions, a divider, then reply, save and more. It overlaps the row above rather than pushing content down.
Thread summary chip
Stacked participant avatars, "N replies" in the accent at 900, then the last-reply time. The phone version keeps one avatar and grows to 36px.
md and up
JWMC3 repliesLast reply 10:26 AM< md
JW3 replies10:26 AMHuddle card
Pinned to the foot of the channel list while a huddle is live. It sits on the list panel, so it darkens rather than lightens: black/22% with a white/8% inset ring and a white Join.