Meowland docs
Workbench

Component reference

ChatAttachments

Files attached to a message or a draft, with preview, download and remove actions.

Preview

Materials you can revisit

Sent materials keep preview and download; no draft-removal action is offered.

packages/meowtail/stories/chat-input.stories.tsxline 216tsx
render: () => (
  <Measure width="content">
    <Stack gap="compact">
      <strong>Workspace reading order</strong>
      <p>
        The attachments are the basis for this discussion. Open the image to see its original
        proportions, and download the record to revisit the checked items.
      </p>
      <ChatAttachments label="Sent materials" items={sampleFiles} />
    </Stack>
  </Measure>
)

Installation

bun add meowtail

Usage

Taken from the examples on this page.

usage.tsxtsx
import { ChatAttachments } from "meowtail";

<ChatAttachments
  label="Host download callback sample"
  items={[
    {
      id: "host-download",
      name: "protected-document.txt",
      mediaType: "text/plain",
      url: "data:text/plain,Host%20download%20example",
      onDownload: () =>
        downloadFeedback.set("The host took over the download; local sample only."),
    },
  ]}
/>

Examples

The other stories that show this component.

Ratios, sizes, and missing information

Wide images, tall images, and files each use their own carrier. Downloads are built-in samples; no local file is read.

packages/meowtail/stories/chat.stories.tsxline 464tsx
render: () => {
  const downloadFeedback = signal("Host download callback sample; no service is connected.");
  return (
    <div className="workshop-composition">
      <ChatAttachments
        label="Image ratio samples"
        items={[
          {
            id: "wide",
            name: "wide-structure-diagram.svg",
            mediaType: "image/svg+xml",
            url:
              "data:image/svg+xml," +
              encodeURIComponent(
                '<svg xmlns="http://www.w3.org/2000/svg" width="600" height="120" viewBox="0 0 600 120"><defs><linearGradient id="bg" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#e0e7ff"/><stop offset="1" stop-color="#fae8ff"/></linearGradient></defs><rect width="600" height="120" fill="url(#bg)"/><text x="300.0" y="60.0" text-anchor="middle" dominant-baseline="middle" font-family="system-ui, sans-serif" font-size="22" font-weight="500" fill="#4f46e5" fill-opacity="0.7">600 × 120</text></svg>',
              ),
          },
          {
            id: "tall",
            name: "tall-structure-diagram.svg",
            mediaType: "image/svg+xml",
            url:
              "data:image/svg+xml," +
              encodeURIComponent(
                '<svg xmlns="http://www.w3.org/2000/svg" width="120" height="360" viewBox="0 0 120 360"><defs><linearGradient id="bg" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#e0e7ff"/><stop offset="1" stop-color="#fae8ff"/></linearGradient></defs><rect width="120" height="360" fill="url(#bg)"/><text x="60.0" y="180.0" text-anchor="middle" dominant-baseline="middle" font-family="system-ui, sans-serif" font-size="16" font-weight="500" fill="#4f46e5" fill-opacity="0.7">120 × 360</text></svg>',
              ),
          },
        ]}
      />
      <ChatAttachments
        label="File size samples"
        items={[
          {
            id: "zero",
            name: "empty.txt",
            mediaType: "text/plain",
            sizeBytes: 0,
            url: "data:text/plain,",
          },
          {
            id: "kb",
            name: "component-design-language-and-all-interaction-state-check-records-with-follow-up-change-suggestions.md",
            mediaType: "text/markdown",
            sizeBytes: 2860,
            url: "data:text/plain,Example",
          },
          {
            id: "mb",
            name: "reference.pdf",
            mediaType: "application/pdf",
            sizeBytes: 1572864,
            url: "data:text/plain,Fixture%20metadata%20only",
          },
          {
            id: "unknown",
            name: "size-not-provided.txt",
            mediaType: "text/plain",
            url: "data:text/plain,Unknown%20size",
          },
        ]}
      />
      <Note>
        File sizes are fixed display data and do not claim to match the actual byte count of the
        downloaded content.
      </Note>
      <ChatAttachments
        label="Host download callback sample"
        items={[
          {
            id: "host-download",
            name: "protected-document.txt",
            mediaType: "text/plain",
            url: "data:text/plain,Host%20download%20example",
            onDownload: () =>
              downloadFeedback.set("The host took over the download; local sample only."),
          },
        ]}
      />
      <Note role="status" data-attachment-download-feedback>
        {downloadFeedback}
      </Note>
      <ChatAttachments label="No attachments" items={[]} />
    </div>
  );
}

Attachments, sources and execution states

Images are recognized by thumbnail and files show name and size; detailed process and sources expand on demand.

packages/meowtail/stories/language.stories.tsxline 473tsx
render: () => (
  <div className="workshop-composition">
    <ChatAttachments
      items={[
        {
          id: "image",
          name: "layout-sketch.svg",
          mediaType: "image/svg+xml",
          url:
            "data:image/svg+xml," +
            encodeURIComponent(
              '<svg xmlns="http://www.w3.org/2000/svg" width="320" height="180" viewBox="0 0 320 180"><defs><linearGradient id="bg" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#eef2ff"/><stop offset="1" stop-color="#fdf2f8"/></linearGradient></defs><rect width="320" height="180" fill="url(#bg)"/><rect x="20" y="20" width="280" height="172" rx="12" fill="#ffffff" stroke="#e4e4e7"/><circle cx="34" cy="34" r="3.5" fill="#fca5a5"/><circle cx="46" cy="34" r="3.5" fill="#fcd34d"/><circle cx="58" cy="34" r="3.5" fill="#86efac"/><rect x="36" y="56" width="62" height="8" rx="4" fill="#e4e4e7"/><rect x="36" y="72" width="46" height="8" rx="4" fill="#f4f4f5"/><rect x="36" y="88" width="53" height="8" rx="4" fill="#f4f4f5"/><rect x="114" y="52" width="170" height="61" rx="8" fill="#6366f1" fill-opacity="0.14"/><rect x="114" y="125" width="136" height="8" rx="4" fill="#e4e4e7"/><rect x="114" y="141" width="94" height="8" rx="4" fill="#f4f4f5"/></svg>',
            ),
        },
        {
          id: "file",
          name: "component-design-language-and-interaction-states.md",
          mediaType: "text/markdown",
          sizeBytes: 2468,
          url: "data:text/plain,MeowUI%20design%20sample",
          onPreview: () =>
            (document.getElementById("attachment-preview") as HTMLDialogElement).showModal(),
        },
      ]}
    />
    <Dialog
      id="attachment-preview"
      labelledBy="attachment-title"
      describedBy="attachment-description"
    >
      <DialogPanel
        header={
          <DialogHeader
            title="component-design-language-and-interaction-states.md"
            titleId="attachment-title"
            description="Built-in Markdown example · No local file was read"
            descriptionId="attachment-description"
          />
        }
      >
        <article className="workshop-document">
          <h3>Keep content in the foreground</h3>
          <p>Components own semantics and actions; themes organize size, color and feedback.</p>
          <p>When changing scheme, work in progress stays in place.</p>
        </article>
        <DialogActions>
          <Button data-close-preview>Back to attachments</Button>
        </DialogActions>
      </DialogPanel>
    </Dialog>
    <ChatToolCall
      icon={<Icon name="file-text" />}
      label="Check component states"
      state="running"
      detail="This is a fixed-state example; no background task runs."
    />
    <ChatToolCall
      icon={<Icon name="file-text" />}
      label="Waiting to confirm the change scope"
      state="waiting"
      collapsible
    >
      <p>The component contract stays stable, and themes can evolve independently.</p>
    </ChatToolCall>
    <ChatToolCall
      icon={<Icon name="network" />}
      label="Example connection lost"
      state="error"
      detail="Input is kept; retry once the connection returns."
    />
    <ChatSources
      items={[
        {
          title: "Apple Human Interface Guidelines",
          url: "https://developer.apple.com/design/human-interface-guidelines/typography",
          description:
            "Desktop type role reference; the Web parameters are this project's adaptation.",
        },
      ]}
    />
  </div>
)

API reference

Props

PropTypeDefault
itemsRequiredPlain arrays are fixed for the mount. A signal updates rows in place.
MaybeSignal<readonly ChatAttachmentItem[]>–
label
string"Message attachments"

Also takes className, children and the attributes and events of its root element.

DOM parts

rootimagepreviewfileicondetailsnamesizeentrypreviewButtonremove

Registry

The component is registered as chat-attachments in the meowtail component registry.