Attachment

A file, image or recording — a thumbnail, a name, a size, an upload state and a way to take it back out.

Open Source

Installation

Attachment ships with the library — install it once, then import:

import { Attachment } from "@delacour/react-native-ui/attachment";

Or copy the source into your project, to own and edit it:

bunx delacour@latest add attachment

Usage

app/index.tsx
import { Attachment } from "@/components/ui/attachment";
import { useState } from "react";
import { View } from "react-native";

export default function Example() {
  const [isAttached, setIsAttached] = useState(true);

  return (
    <View className="flex-1 justify-center p-4">
      {isAttached ? (
        <Attachment
          kind="file"
          name="quarterly-report.pdf"
          sizeBytes={2516582}
          onRemove={() => setIsAttached(false)}
        />
      ) : null}
    </View>
  );
}

Kinds

kind picks the glyph and its tint; an image needs a source, a video's poster is optional, and a file reads its extension from name.

Kinds
Kinds
<Attachment kind="image" name="harbour.jpg" source={{ uri: photo.uri }} sizeBytes={2516582} />
<Attachment kind="video" name="teaser.mov" durationMs={42000} />
<Attachment kind="audio" name="Voice memo" durationMs={95000} />
<Attachment kind="file" name="report.pdf" sizeBytes={482304} />

Layouts

chip is the composer pill, tile a square thumbnail for a photo tray, and card a full-width row for inside a message.

Layouts
Layouts
<Attachment kind="file" layout="chip" name="site-plan.pdf" onRemove={remove} />
<Attachment kind="image" layout="tile" name="terrace.jpg" source={{ uri: photo.uri }} />
<Attachment kind="file" layout="card" name="contract.docx" onPress={open} />

Sizes

size sets the preview square and the tile in fixed points, so a tray lines up.

<Attachment kind="file" name="spec.pdf" size="sm" />
<Attachment kind="file" name="spec.pdf" size="md" />
<Attachment kind="file" name="spec.pdf" size="lg" />

Upload states

The app owns the upload and hands the attachment its status; remove becomes cancel while uploading, and retry appears in error.

Upload states
<Attachment
  kind="file"
  name={file.name}
  sizeBytes={file.size}
  status={
    file.error
      ? { type: "error", message: file.error }
      : file.sent < file.size
        ? { type: "uploading", progress: file.sent / file.size }
        : { type: "done" }
  }
  onRemove={() => cancel(file.id)}
  onRetry={() => retry(file.id)}
/>

Composer tray

Attachment.Group scrolls sideways, hands its layout and size down, and slides the rest along when one is removed.

A composer tray
<Attachment.Group layout="tile">
  {drafts.map((draft) => (
    <Attachment
      key={draft.id}
      kind="image"
      name={draft.name}
      source={{ uri: draft.uri }}
      onRemove={() => drop(draft.id)}
    />
  ))}
</Attachment.Group>

Key each attachment by its file's id, not its index, or removing the first one reuses its cell for the second and the slide runs backwards.

Inside a message

A card takes the width of whatever holds it, so a message bubble only needs to render it as a child.

Inside a message
Inside a message
<View className="max-w-[85%] gap-2 self-start rounded-2xl bg-muted p-2">
  <Text.Paragraph>Here are the revised drawings.</Text.Paragraph>
  <Attachment kind="file" layout="card" name="drawings-rev-c.pdf" onPress={open} />
</View>

Custom parts

Children replace the default parts entirely, and useAttachment() reads the attachment from inside one.

<Attachment kind="file" name="invoice.pdf" sizeBytes={154624} status={status}>
  <Attachment.Preview />
  <Attachment.Content>
    <Attachment.Name />
    <Attachment.Meta />
  </Attachment.Content>
  <Attachment.Progress />
</Attachment>

Accessibility

Each attachment is one element — Photo, harbour.jpg, 2.4 megabytes, uploading with the percentage in ten-point steps — and remove and retry are its actions rather than separate stops.

API Reference

Anatomy

PartWhat it is
AttachmentOwns the media, the status and the axes. One accessible element, with remove and retry as actions
Attachment.PreviewThe kind's glyph on its tint, with the picture over it
Attachment.ContentThe name over the meta line. A frosted strip on a tile
Attachment.NameOne line, cut in the middle so the extension stays
Attachment.Meta2.4 MB · PDF, a recording's length, or the error message
Attachment.ProgressThe upload bar, drawn only while uploading
Attachment.RemoveThe remove control. Reads as cancel while uploading
Attachment.RetryThe retry control, drawn only in error
Attachment.GroupA horizontal tray that animates its attachments in and out

Attachment

Prop

Type

Attachment.Group

Prop

Type

useAttachment

Returns kind, layout, size, status, name, isInteractive and isDisabled for a custom part. Throws outside an Attachment.

On this page