Skip to content
Ink
Esc
↑↓navigate↵open⌘Jpreview
On this page

Conversations

Display messages, replies and a composer.

Example

ConversationScreen provides the message list, composer and actions page.

import { useState } from "react";
import { ConversationScreen, type ConversationMessage } from "ink";

export default function Chat() {
  const [messages, setMessages] = useState<ConversationMessage[]>([
    { id: "1", text: "Have you listened to this album yet?",
      timestamp: Date.now() },
  ]);
  const [draft, setDraft] = useState("");

  return <ConversationScreen
    title="Alex"
    messages={messages}
    draft={draft}
    onDraftChange={setDraft}
    onSend={({ text, replyTo }) => {
      setMessages(current => [...current, {
        id: String(Date.now()), text, timestamp: Date.now(),
        outgoing: true,
        reply: replyTo ? {
          author: replyTo.outgoing ? "You" : "Alex",
          text: replyTo.text || "Photo",
        } : undefined,
      }]);
      setDraft("");
    }}
  />;
}

This example adds messages locally. Connect onSend to your messaging service to send them.

An incoming message and an outgoing reply above the composer.

Group chats

Set group and include author on incoming messages. Outgoing messages omit the author.

<ConversationScreen group title="Music club"
  messages={messages} draft={draft}
  onDraftChange={setDraft} onSend={send} />
A group chat showing Alex beside an incoming message.

Replies

Long-press a message or image, then choose Reply. Ink returns to the conversation and shows a reply banner.

onSend receives { text, replyTo }. Add the outgoing message, save its reply preview and clear the draft. Ink keeps the keyboard open and scrolls to the bottom.

A message preview with Reply and a custom reaction action.
The reply banner above the composer.
An outgoing message quoting an earlier message, with reactions in its information line.

Images

Add image with a source and dimensions. Text is optional.

Tap an image to open it in the full content area with zoom. Back returns to the conversation at the same scroll position. Set onImagePress to replace this behaviour.

{
  id: "photo",
  timestamp: Date.now(),
  image: { src: artwork, width: 180, height: 180 },
}
An incoming image-only message.

Message status

Set status on outgoing messages.

Status Display
sending Single tick in inactive grey
sent Single tick
delivered Double tick
read Eye
failed “Tap to retry” when onRetry is supplied; otherwise “Could not send”
<Message text="See you soon" timestamp={Date.now()}
  outgoing status="delivered" />

Failure text replaces the timestamp. Failed messages have no icon.

Messages showing sending, sent and delivered states.
Messages showing read and failed states.

Message details

Every message needs a stable id and a timestamp in milliseconds since the Unix epoch. Ink shows local time for today and adds the month and day for older messages. reactions adds text to the same information line.

Use actions(message) for extra message actions, onAttach for the plus button, and onDoubleTap for double taps. Ink returns to the chat before running a custom action.

Set rightAction={{ icon, onPress }} to add a header action, as with Screen.

onLoadOlder returns a promise and prepends older messages; hasOlder says whether more remain. Ink loads history and nearby images ahead of view, preserving scroll position and momentum.

loading shows initial loading content; sending disables the send action.

Message is also available separately for custom screens.

Messages detect and underline links automatically. Tapping opens the in-app browser, or the default browser if unavailable. Set onLinkPress to override this behaviour.

import { Message } from "ink";

<Message
  text="Have a look at https://ink.noscroll.ing"
  timestamp={Date.now()}
/>
A detected link kept together on its own line below the message text.

Tapping a card opens its URL. Use onPress for a custom action. Supply the title and image yourself; Ink does not fetch page metadata.

import { LinkPreview } from "ink";

<LinkPreview
  url="https://www.youtube.com/watch?v=-zjJpFYtx9s"
  title="Arson as a Christmas Tradition: The Gävle Goat"
  image={{
    src: "https://i.ytimg.com/vi/-zjJpFYtx9s/hqdefault.jpg",
    width: 480,
    height: 360,
  }}
/>
A YouTube preview card with the Gävle Goat thumbnail, video title and domain.

Use icon={favicon} to add a site icon.

In a conversation, pass the same data as the message’s linkPreview. The card replaces its URL in the displayed text. Tapping uses onLinkPress if supplied; long-pressing opens message actions.