Attachment
File attachment with preview, upload progress, removal and error state.
Installation
npx axiom add attachmentpnpm dlx axiom add attachmentyarn dlx axiom add attachmentbun x axiom add attachmentAlso copies: icon, icon-button, spinner, text and tappable.
Requires the close, error, file, image and refresh icons in your icon registry. Colors come from attachment.{row,tile}.{default,uploading,done,error} in the component tokens.
Usage
import { Attachment, AttachmentTile } from "@/components/ui/attachment";
<Attachment
file={file}
progress={uploadProgress}
error={uploadError}
onRemove={() => removeFile(file.id)}
onRetry={() => upload(file)}
/>;
<AttachmentTile file={photo} progress={photoProgress} onRemove={removePhoto} />;Two components share the upload logic of use-attachment.ts and take the same props:
| Component | What it is |
|---|---|
Attachment | A row for a document: thumbnail or icon, name, size or error, and a progress bar while it uploads. |
AttachmentTile | A 72pt square for a grid of photos: a spinner while it uploads, a remove button in the corner. |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
file | { name: string; size?: number; mimeType?: string; uri?: string } | — | The file to show. An image/* or video/* mimeType gets the image icon and, with a uri, a thumbnail; anything else gets file. |
status | 'idle' | 'uploading' | 'done' | 'error' | derived | Upload state. Derived from progress and error when not set. |
progress | number | — | Upload progress from 0 to 1. A bar on a row, a spinner on a tile. |
error | string | boolean | — | Error state: red border and the message instead of the size. |
onPress | () => void | — | Opens the file, for example in a preview. |
onRemove | () => void | — | Shows a remove button. While uploading, it’s up to you to cancel the request. |
onRetry | () => void | — | In the error state, pressing the attachment or the retry icon calls it. |
formatSize | (bytes: number) => string | 1.2 MB style | Custom size formatting. |
style | StyleProp<ViewStyle> | — | Extra styles for the container. |
Axiom handles the UI and its states. The upload itself stays in your app’s code: pass progress and error from your upload logic.
Use cases
Attachments in a message composer
Tiles above the input, each with its own upload state, before the message is sent.
<ScrollView horizontal>
{drafts.map((d) => {
const Preview = isMedia(d) ? AttachmentTile : Attachment;
return (
<Preview
key={d.id}
file={d.file}
progress={d.progress}
onRemove={() => removeDraft(d.id)}
/>
);
})}
</ScrollView>Documents required by a form
A dashed placeholder to add a file, replaced by the attachment once picked.
{
back ? (
<Attachment
file={back.file}
progress={back.progress}
error={back.error}
onRemove={() => setBack(null)}
/>
) : (
<UploadPlaceholder
icon="camera"
label="Take a photo or choose a file"
onPress={pickBack}
/>
);
}Upload failure
The error replaces the size, and the whole attachment becomes a retry button.
<Attachment
file={file}
error={
error?.code === "too_large"
? "File too large (max 25 MB)"
: "Connection lost · Tap to retry"
}
onRetry={error?.code === "too_large" ? undefined : () => upload(file)}
onRemove={() => remove(file.id)}
/>Related
- BottomSheet, for a “Take photo / Choose file” picker
- Spinner