Elements

20 / 122 · Messages

Timestamps

Chronology in a long thread: days marked, times on hover.

Yesterday
Why does the draft survive a reload?16:04
It's persisted per thread, so the slot is read back on mount.16:04
Today
And across thread switches?09:12
Each thread owns its own slot, so nothing leaks between them.09:12

Installation

npx shadcn@latest add "@assistant-ui/elements-day-separator"

Usage

import { DaySeparator } from "@/components/elements/day-separator";

<DaySeparator messages={messages} />

Props

DaySeparator

messages*DatedMessage[]Turns in order. A separator is inserted wherever day changes from the previous message, so grouping needs no pre-processing.
classNamestringExtra classes merged onto the root.

DatedMessage

id*stringStable identity for the row key.
role*"user" | "assistant"Who sent it. User turns sit right in a bubble, assistant turns run flush left.
text*stringMessage body.
day*stringPre-formatted day label. Compared against the previous message to decide where a rule goes.
time*stringPre-formatted time, revealed on hover.

Source

day-separator.tsx
"use client";

import { cn } from "@/lib/utils";
import { mono } from "./surfaces";

export interface DatedMessage {
  id: string;
  day: string;
  time: string;
  role: "user" | "assistant";
  text: string;
}

export function DaySeparator({
  messages,
  className,
}: {
  messages: readonly DatedMessage[];
  className?: string;
}) {
  let lastDay = "";

  return (
    <div className={cn("flex w-full max-w-sm flex-col gap-2", className)}>
      {messages.map((message) => {
        const newDay = message.day !== lastDay;
        lastDay = message.day;

        return (
          <div key={message.id} className="flex flex-col gap-2">
            {newDay && (
              <div className="flex items-center gap-2.5 py-1">
                <span className="bg-foreground/[0.08] h-px flex-1" />
                <span className={cn(mono, "text-foreground/30")}>
                  {message.day}
                </span>
                <span className="bg-foreground/[0.08] h-px flex-1" />
              </div>
            )}
            <div
              className={cn(
                "group flex items-baseline gap-2",
                message.role === "user" && "flex-row-reverse",
              )}
            >
              <span
                className={cn(
                  "max-w-[80%] text-[13.5px] leading-relaxed",
                  message.role === "user"
                    ? "bg-foreground/[0.05] rounded-2xl px-3.5 py-2"
                    : "text-foreground/75",
                )}
              >
                {message.text}
              </span>
              <span
                className={cn(
                  mono,
                  "text-foreground/0 group-hover:text-foreground/30 shrink-0 tabular-nums transition-colors",
                )}
              >
                {message.time}
              </span>
            </div>
          </div>
        );
      })}
    </div>
  );
}