Skip to content
txbrendan/ui

Read By

beta

How many have read a message, out of how many it went to, with a peek that reminds the rest.

Preview

Team sync moves to Room 2 on Thursday. Bring your laptop to share.

many

all read

Read by all 23

nobody

Sent to no one yet

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/read-by

Use it for

  • Under a message, a notice, or a form people were asked to read: "Read by 241 of 280".
  • Pass everyone it went to in people. Mark read: true, or the time, like "10:12", once they have.
  • Its peek lists who has not read it yet, each with Remind, and Remind all for two or more.
  • Send the reminders in onRemind. It runs once the 8-second undo window ends, never on the press.
  • Once everyone has read it, it says "Read by all 23" as plain text, with no peek.

Avoid it for

  • A count with no one behind it, like page views. Use plain text.
  • Showing who has read it. The peek is for the ones who have not.

Props

  • people

    Required. Everyone it went to. read is true, or when they read it, once they have.

    Array<{ person: string; photo?: string; meta?: string; read?: boolean | string }>

  • onRemind

    Reminds people who have not read it, once the undo window ends. Remind all leaves out anyone already reminded.

    (people: { person: string; photo?: string }[]) => void

  • open

    Controlled. The peek shows while true. Hover, focus, and Esc only ask, through onOpenChange.

    boolean

  • defaultOpen

    Starts with the peek open, without taking focus.

    boolean · default false

  • onOpenChange

    Hears the peek open and close, or what it asks for when controlled.

    (open: boolean) => void

  • className

    Extra classes on the text. Later classes win.

    string

Accessibility

  • With people still to read it, the text is a focusable button that opens a dialog. Its name is the count, like "Read by 241 of 280".
  • The peek is named by its title, like "39 haven't read it yet". Enter moves into it, and Esc closes it and puts focus back.
  • Each Remind says what it did in the page's one polite live region. Undo arms 1.2 seconds after a key press, and ⌘Z or Ctrl+Z takes back the newest reminder.
  • Once everyone has read it, the text is plain, with no button and no Tab stop.

Examples

Under a message

<ReadBy
  people={[
    { person: "Kate Clarke", read: "10:12" },
    { person: "Tom Hale", read: true },
    { person: "Ava Moss", meta: "Design team" },
    { person: "Rob Kerr", meta: "Support team" },
  ]}
  onRemind={(people) => console.log("remind", people)}
/>