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 } from "@/components/ui/attachment";
<Attachment
file={file}
progress={uploadProgress}
error={uploadError}
onRemove={() => removeFile(file.id)}
onRetry={() => upload(file)}
/>;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. Shows a bar (row) or a ring (tile). |
error | string | boolean | — | Error state: red border and the message instead of the size. |
variant | 'row' | 'tile' | 'row' | row for documents with a name, tile for a square thumbnail in a grid of photos. |
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) => (
<Attachment
key={d.id}
variant={isMedia(d) ? "tile" : "row"}
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