Skip to content

Activity & conversations

What people said and did: threads, doc comments, feeds, inboxes, versions, chat, calls and feedback.

Installation & usage

npx @youj/cli add activity-comment-thread

Also installs alert-dialog, avatar, avatar-stack, badge, button, card, collapse, counter, dropdown-menu, empty, icon-at-sign, icon-bell, icon-bell-off, icon-bold, icon-check, icon-chevron-down, icon-circle-alert, icon-code, icon-engine, icon-italic, icon-link, icon-loader-dots, icon-message, icon-more-horizontal, icon-pencil, icon-reply, icon-smile-plus, icon-trash, input-group, kbd, popover, presence-list, skeleton, toggle, tooltip, use-announcer, use-controllable-state, use-now, use-visible, youj-core, youj-presets, youj-ui-core.

The CLI copies the block to blocks/activity/comment-thread.tsx with every component it uses, so it’s yours to edit.

Every prop is optional and falls back to the demo, so it renders as you see it until you pass your own. Give it your data, rewrite or translate its strings through copy, and connect its actions:

commentThread-view.tsx
'use client'

import { CommentThread, type CommentThreadProps } from '@/blocks/activity/comment-thread'

export function CommentThreadView({ threads, people, user, typing }: Pick<CommentThreadProps, 'threads' | 'people' | 'user' | 'typing'>) {
  return (
    <CommentThread
      threads={threads}
      people={people}
      user={user}
      typing={typing}
      copy={{ title: 'Discussion' }}
      onSubscribedChange={(subscribed) => {
        // your code
      }}
      onRetry={() => {
        // your code
      }}
      onPost={async (draft) => {
        // await your API here; the block shows progress until it settles
      }}
    />
  )
}

Reference

Props29 props, 5 types

Props

CommentThread props
PropType
themeThemeInput

Wraps the block in this theme. Default: the theme around it.

–
embeddedboolean

Inside another app: a section named by an h2, without the page around the card.

default true

true
fillboolean

Take the whole width it's given instead of stopping at its natural width (640px at 100% scale).

default false

false
classNames{ card?: string }

Classes for its parts: card holds the block's size (852px tall at 100%; the threads scroll inside it, so replies, reactions, someone typing and a longer draft never resize it).

–
headingLevel1 | 2 | 3

The title's heading level. Default 1, or 2 when embedded.

–
localestring

Formats its times. Default: copy.locale, else the app's locale (YoujProvider), else en-US.

–
copyPartial<CommentThreadCopy>

Any of the block's strings, to rewrite or translate.

–
subjectstring

What the discussion is about, under the title: "Q4 launch plan".

–
threadsCommentThreadItem[]

The threads, oldest first. Default: a launch plan's discussion, with a reply arriving while it's open (a demo, marked).

–
peopleCommentThreadPerson[]

Who can be

default demo

demo
userCommentThreadPerson

The signed-in person: new comments are theirs, and they can edit and delete their own.

default the demo's Jordan

the demo's Jordan
typingCommentThreadPerson[]

People typing right now, from your realtime layer.

default []

[]
reactionsstring[]

The emoji a comment can get.

default 👍 👎 😄 🎉 😕 ❤️ 🚀 👀

👍 👎 😄 🎉 😕 ❤️ 🚀 👀
subscribedboolean

Whether the signed-in person gets notified, when your app owns it; pair with onSubscribedChange.

–
defaultSubscribedboolean

Subscribed at first, when the block owns it.

default true

true
onSubscribedChange(subscribed: boolean) => void–
nowDate | number

The time the first render shows, which the server and the browser agree on; it ticks on once mounted. Default: the demo's, or the browser's clock with your threads.

–
loadingboolean

Shows the threads' shapes while they load.

default false

false
errorReactNode

Why the threads didn't load; shows with Retry when onRetry is set.

–
onRetry() => void–
emptyReactNode

Shown when there are no threads. Default: a quiet "No comments yet".

–
demoMarkerboolean

Shows a dashed "Demo data" badge while the block runs on its demo threads.

default true

true
onPost(draft: CommentThreadDraft) => Promise<Partial<CommentThreadComment> | void> | Partial<CommentThreadComment> | void

A new comment (threadId null) or a reply. Resolve with the saved comment (its id, createdAt) to replace the one shown.

–
onEdit(commentId: string, body: string) => Promise<unknown> | unknown–
onDelete(commentId: string) => Promise<unknown> | unknown–
onReact(commentId: string, emoji: string, reacted: boolean) => Promise<unknown> | unknown–
onResolve(threadId: string, resolved: boolean) => Promise<unknown> | unknown–
onCopyLink(commentId: string) => void

A comment's Copy link. Default: this page's address with the comment's anchor.

–
actionsReactNode

More controls at the end of the header, after Subscribe: the host's own actions on the discussion (Resolve, Delete).

–

CommentThreadItem

CommentThreadItem props
PropType
idRequiredstring–
commentsRequiredCommentThreadComment[]

The first is the thread's own comment; the rest are its replies, oldest first.

–
resolved{ by: CommentThreadPerson; at: Date | number | string } | null

Who resolved it and when. A resolved thread folds to one line.

–

CommentThreadComment

CommentThreadComment props
PropType
idRequiredstring–
authorRequiredCommentThreadPerson–
bodyRequiredstring

The text, in markdown-lite: **bold**, _italic_, ~~strike~~, code, [links](https://…), lists, > quotes and

–
createdAtRequiredDate | number | string–
editedAtDate | number | string

When it was last edited; shows "edited".

–
reactionsCommentThreadReaction[]–
deletedboolean

Deleted while it had replies: shown as a placeholder so the replies keep their context.

–

CommentThreadPerson

CommentThreadPerson props
PropType
idRequiredstring–
nameRequiredstring–
handlestring

What follows @ when they're mentioned: "mara". Default: their id.

–
avatarstring

An image URL for their avatar. Default: their initials.

–

CommentThreadReaction

CommentThreadReaction props
PropType
emojiRequiredstring–
byRequiredstring[]–

CommentThreadDraft

CommentThreadDraft props
PropType
threadIdRequiredstring | null

The thread it replies to; null for a new thread.

–
bodyRequiredstring–
mentionsRequiredstring[]

The ids of the people it

–
Strings71 keys for copy

Strings

copy takes any of these keys; the rest keep their defaults. A key whose text holds a value (a count, a name) takes a function.

titledemoDatacountparticipantssubscribesubscribedsubscribeHintjustNoweditededitedAtsendingsendFailedretrydiscardfaileddeletednewLabelnewPlaceholderreplyLabelreplyPlaceholdereditLabelsendsendReplysavecancelcomposerHintcomposerHintOtherformattingbolditaliccodementionmentionListmentionResultsactionsreactreplymoreeditcopyLinkdeleteresolvereopendeleteTitledeleteDescriptiondeleteConfirmresolvedByshowThreadhideThreadmoreRepliesrepliesemojiNamesreactionsyouothersreactedWithreactionLabeltypingemptyTitleemptyDescriptionloadingerrorTitleannounceSentannounceReplyannounceCommentannounceDeletedannounceEditedannounceResolvedannounceReopenedannounceLinkCopiedlocale
Built from17 components
Keyboardcomposer, mentions, threads, feed, versions, triage

Keyboard

WhereKeysWhat they do
Any composer⌘/Ctrl + EnterSend
EscCancel an edit or a reply
↑ in an empty composerEdit your last comment
⌘/Ctrl + B, I, EBold, italic, code
@Mention someone: ↑ ↓ to choose, Enter or Tab to insert, Esc to close
Doc comments⌘⌥M / Ctrl+Alt+MComment on the selected words
Alt + ↑ / ↓Previous or next thread
Enter on a highlightOpen its thread and go to it
Activity feedPageUp / PageDownPrevious or next row
Ctrl + Home / EndLeave the feed before its start or after its end
Version history↑ ↓, Home, End in the listPick a version
Alt + ↑ / ↓Previous or next change
Inbox↑ ↓ or J K, Home, EndMove between notifications
EnterOpen (and mark read)
E, S, R, UDone, snooze, reply, read or unread
Accessibilityarticles, feeds, live regions, motion

Accessibility

Comments are articles named by their author and described by their time, in lists. The composer is a labelled textarea whose shortcuts are its description; the mention list is a listbox the field drives with aria-activedescendant, so focus never leaves the text, and the number of matches is announced. Reactions are toggle buttons (aria-pressed) named with the emoji's name, the count and who reacted. Sent, edited, deleted and resolved comments, new replies and failures are announced; failures use role="alert".

In Doc comments each highlight is a button in the text, named by its words and described by its comments, one tab stop per thread; the rail is a list in document order, each thread a group named by its quote.

The activity feed is a WAI-ARIA feed of articles with aria-posinset and aria-setsize (−1 while older events can load), busy while they load, under a heading per day. Arrivals are announced once, whether they come in at once or wait behind the pill.

The inbox's rows are one tab stop between them (the current row's tools join it), named by their sentence with "Unread" first when they are; its keys are listed in each tool's aria-keyshortcuts.

The version list is a listbox with groups per day; changes are real ins and del elements that say "Added" and "Removed" to a screen reader, each a stop for Previous and Next change.

The support chat is an aside named for the product. On desktop the panel is a popover, on phones a full-screen sheet; Escape closes it and focus returns to the launcher. A polite role="status" line reports the header's status.

The voice call's orb is an image named by the agent and its state, and state changes are announced politely; the clock is a role="timer", Mute is a toggle with aria-pressed, the volume is a labeled slider and the voice level a meter, End call and Call again are named buttons, and the caption line is a polite live region. Muting grays the orb and empties the meter while the agent listens.

The survey card is a labelled region that does not steal focus when it appears, and focus returns to the page when it leaves. The 0 to 10 scale is one radio group: each score is named by its number, the arrow keys move through it. The feedback widget's type is a labelled segmented control, the message sends with Cmd or Ctrl and Enter.

Under reduced motion new rows and comments appear in place, threads fold instantly, reactions don't pop and the rail's cards jump to their places.

Under reduced motion cards appear in place and replies arrive whole.