# File uploader: Validation

> Limit what the slotsmith FileUploader accepts with accept, maxSize, minSize, maxFiles and your own validate rule; refused files are explained and announced.

The file uploader checks every picked or dropped file against `accept`, `maxSize`, `minSize`, `maxFiles` and your own `validate` rule. A refused file is never added; its reason is listed under the uploader, in a polite live region.

```tsx
import { FileUploader, useFileUploaderContext } from "slotsmith/file-uploader";

const KB = 1024;
const MB = 1024 * KB;

/** A file made in the page, so each rule can be tried without finding one on disk. */
const make = (name: string, type: string, size: number) => new File([new Uint8Array(size)], name, { type });

/** Buttons that add made-up files, as a drop would. Inside the provider, so they can call `add`. */
function TryFiles() {
  const { add } = useFileUploaderContext();
  const tries = [
    { label: "A 6 MB photo", files: () => [make("holiday.jpg", "image/jpeg", 6 * MB)] },
    { label: "A text file", files: () => [make("notes.txt", "text/plain", 2 * KB)] },
    { label: "An empty image", files: () => [make("blank.png", "image/png", 10)] },
    { label: "A draft", files: () => [make("cover-draft.png", "image/png", 80 * KB)] },
    {
      label: "Four photos",
      files: () => ["one", "two", "three", "four"].map((name) => make(`${name}.jpg`, "image/jpeg", 120 * KB)),
    },
  ];

  return (
    <div role="group" aria-label="Try a file" style={{ display: "flex", flexWrap: "wrap", gap: "0.5rem" }}>
      {tries.map((entry) => (
        <button key={entry.label} type="button" onClick={() => add(entry.files())} style={{ background: "transparent" }}>
          {entry.label}
        </button>
      ))}
    </div>
  );
}

export default function Validation() {
  return (
    <FileUploader.Provider
      multiple
      // The same tokens as the native input: extensions, MIME types or `image/*`.
      accept="image/png,image/jpeg"
      // Sizes are in bytes.
      maxSize={5 * MB}
      minSize={1 * KB}
      // The most items the list may hold, counting the ones already in it.
      maxFiles={3}
      // Your own rule: return a message to refuse the file.
      validate={(file) => (file.name.includes("draft") ? `${file.name} is a draft. Upload the final version.` : null)}
    >
      <div style={{ display: "grid", gap: "0.75rem" }}>
        <TryFiles />
        <FileUploader.Root>
          <FileUploader.Dropzone />
          <FileUploader.List />
          <FileUploader.Rejections />
        </FileUploader.Root>
      </div>
    </FileUploader.Provider>
  );
}
```

The buttons add made-up files, as a drop would, so each rule can be tried without finding a file. Add a 6 MB photo: it is refused as too large. Add four photos: three fit and the fourth is refused.

## The rules

Each file is checked in this order, and the first rule it breaks is the one reported:

1. **[`maxFiles`](https://slotsmith.dev/components/file-uploader/api/#prop-maxFiles)**: how many items the list may hold, counting the ones already in it. Without [`multiple`](https://slotsmith.dev/components/file-uploader/api/#prop-multiple) the limit is 1, and a new file replaces the old one instead of being refused. Dropping twenty files into a list with room for three keeps the first three.
2. **[`accept`](https://slotsmith.dev/components/file-uploader/api/#prop-accept)**: the same tokens as the native input, as a string or an array: an extension (`.pdf`), a MIME type (`image/png`) or a wildcard (`image/*`). The browser applies it only to the file dialog, so dropped files are checked here too.
3. **[`maxSize`](https://slotsmith.dev/components/file-uploader/api/#prop-maxSize)** and **[`minSize`](https://slotsmith.dev/components/file-uploader/api/#prop-minSize)**, in bytes: `5 * 1024 * 1024` is 5 MB.
4. **[`validate`](https://slotsmith.dev/components/file-uploader/api/#prop-validate)**: your own rule. Return a message to refuse the file, or nothing to allow it.

Sizes are shown with 1024 per step, `5 MB` or `512 KB`, in the hint and in the messages.

## The messages

Refused files are listed by the `Rejections` slot, a `role="status"` region with `aria-live="polite"`. New refusals are added to the list, and the Dismiss button clears it. The region is created with the first refusal, so some screen readers announce only the later ones; this is a known library issue. [`onReject`](https://slotsmith.dev/components/file-uploader/api/#prop-onReject) is called with each batch, as `{ file, reason, message }`, for your own handling.

The wording comes from the [validation messages](https://slotsmith.dev/components/file-uploader/api/#validation-labels): `wrongType`, `tooLarge`, `tooSmall` and `tooMany`. Override them with [`validationLabels`](https://slotsmith.dev/components/file-uploader/api/#prop-validationLabels), or let a locale pack translate them; a pack also writes the sizes in its language's number format.

## The compound parts

This demo is built from the compound parts, so the buttons can reach the uploader: `FileUploader.Provider` holds the state, and `TryFiles` reads `add` from `useFileUploaderContext()`. `FileUploader.Root`, `.Dropzone`, `.List` and `.Rejections` render the same parts as the default layout. The [overview](https://slotsmith.dev/components/file-uploader/#reference) lists every part.
