# V button 28

> File upload button with attachment preview and clear action

A shadcn/ui block from the [@cnippet](https://ui.cnippet.dev/) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cnippet/v-button-28).

- Page, with a live preview: https://shadcn.exchange/r/cnippet/v-button-28
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/cnippet/v-button-28-light-42816e60fcf561fa.png
- Registry item: https://ui.cnippet.dev/r/v-button-28.json
- Categories: button
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.cnippet.dev/r/v-button-28.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@cnippet/button`

## Files

### `registry/default/variants/v-button-28.tsx`

```tsx
"use client";

import { PaperclipIcon, XIcon } from "lucide-react";
import { useRef, useState } from "react";
import { Button } from "@/registry/default/ui/button";

export function Pattern() {
  const inputRef = useRef<HTMLInputElement>(null);
  const [fileName, setFileName] = useState<string | null>(null);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    setFileName(file?.name ?? null);
  };

  const handleClear = () => {
    setFileName(null);
    if (inputRef.current) inputRef.current.value = "";
  };

  return (
    <div className="flex flex-col items-start gap-3">
      <input
        accept="image/*,.pdf,.doc,.docx"
        className="sr-only"
        id="file-upload"
        onChange={handleChange}
        ref={inputRef}
        type="file"
      />
      <Button onClick={() => inputRef.current?.click()} variant="outline">
        <PaperclipIcon className="size-4" />
        {fileName ? "Change file" : "Attach file"}
      </Button>
      {fileName && (
        <div className="flex items-center gap-2 rounded-lg border bg-muted/50 px-3 py-1.5 text-sm">
          <PaperclipIcon className="size-3.5 shrink-0 text-muted-foreground" />
          <span className="max-w-45 truncate">{fileName}</span>
          <button
            aria-label="Remove file"
            className="ml-1 text-muted-foreground hover:text-foreground"
            onClick={handleClear}
            type="button"
          >
            <XIcon className="size-3.5" />
          </button>
        </div>
      )}
    </div>
  );
}
```
