# Attachment: Upload flow

> Pass `progress` while `state="uploading"` to draw a thin progress bar along the bottom edge. Image previews stay dimmed until the upload is done.

Example: https://hextaui.com/docs/attachment/examples/upload-flow
Component docs: https://hextaui.com/docs/attachment.md

## Add this example

```bash
npx shadcn@latest add https://hextaui.com/r/attachment-upload-flow.json
```

This adds `components/examples/attachment/upload-flow.tsx` with the Attachment component and the HextaUI theme tokens.

## Source

```tsx title="components/examples/attachment/upload-flow.tsx"
"use client"

import * as React from "react"
import { IconTable } from "@tabler/icons-react"

import {
  Attachment,
  AttachmentContent,
  AttachmentDescription,
  AttachmentMedia,
  AttachmentTitle,
  type AttachmentState,
} from "@/components/ui/attachment"
import { Button } from "@/components/ui/button"

const descriptions: Record<AttachmentState, string> = {
  idle: "Ready to upload",
  uploading: "Uploading…",
  processing: "Processing",
  error: "Upload failed · connection lost",
  done: "CSV · 840 KB",
}

export function AttachmentUploadFlow() {
  const [state, setState] = React.useState<AttachmentState>("idle")
  const [progress, setProgress] = React.useState(0)
  const timer = React.useRef<ReturnType<typeof setInterval>>(undefined)

  React.useEffect(() => () => clearInterval(timer.current), [])

  const start = (fail: boolean) => {
    clearInterval(timer.current)
    setState("uploading")
    setProgress(0)
    let value = 0
    timer.current = setInterval(() => {
      value += 12
      if (fail && value >= 60) {
        clearInterval(timer.current)
        setState("error")
        return
      }
      if (value >= 100) {
        clearInterval(timer.current)
        setProgress(100)
        setState("processing")
        setTimeout(() => setState("done"), 900)
        return
      }
      setProgress(value)
    }, 250)
  }

  return (
    <div className="flex flex-col items-start gap-3">
      <Attachment state={state} progress={progress}>
        <AttachmentMedia>
          <IconTable />
        </AttachmentMedia>
        <AttachmentContent>
          <AttachmentTitle>customers-2026.csv</AttachmentTitle>
          <AttachmentDescription>
            {state === "uploading"
              ? `Uploading… ${progress}%`
              : descriptions[state]}
          </AttachmentDescription>
        </AttachmentContent>
      </Attachment>
      <div className="flex gap-2">
        <Button size="sm" onClick={() => start(false)}>
          Start upload
        </Button>
        <Button size="sm" variant="outline" onClick={() => start(true)}>
          Upload that fails
        </Button>
      </div>
    </div>
  )
}
```

## Props in this example

### Attachment

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `state` | `"idle" \| "uploading" \| "processing" \| "error" \| "done"` | `"done"` |  |
| `progress` | `number` | – | 0 to 100. Shown only while uploading, and clamped to that range. |

## More Attachment examples

- [Image](https://hextaui.com/docs/attachment/examples/image.md)
- [States](https://hextaui.com/docs/attachment/examples/states.md)
- [Sizes](https://hextaui.com/docs/attachment/examples/sizes.md)
- [Long names](https://hextaui.com/docs/attachment/examples/long-names.md)
- [Group](https://hextaui.com/docs/attachment/examples/group.md)
- [Trigger](https://hextaui.com/docs/attachment/examples/trigger.md)
- [Composer](https://hextaui.com/docs/attachment/examples/composer.md)
- [Broken image](https://hextaui.com/docs/attachment/examples/broken-image.md)
- [Stress](https://hextaui.com/docs/attachment/examples/stress.md)
- [Right to left](https://hextaui.com/docs/attachment/examples/rtl.md)
