SvFileUpload

A drag-and-drop file field with click-to-browse fallback, accept / size / count validation, and a selected-files list.

SvFileUpload is a themed dropzone: drop files onto it or click to open the native picker, and it validates each against accept, maxSize, and maxFiles, firing onReject for the ones it turns away. It is part of the editor kit, so it carries the shared contract - label, hint, error, required, dir / RTL, and localizable messages - through SvField. It is controlled via files and onChange.

Basic usage

<script lang="ts">
  import { SvFileUpload, type FileRejection } from '@svgrid/grid'
  let files = $state<File[]>([])
</script>

<SvFileUpload
  {files}
  label="Attachments"
  accept="image/*,.pdf"
  multiple
  maxSize={5 * 1024 * 1024}
  onChange={(f) => (files = f)}
  onReject={(r: FileRejection[]) => console.warn(r)}
/>

Props

Prop Type Default Description
files File[] [] Selected files (bindable). Controlled via onChange.
onChange (files: File[]) => void - Fired when the selection changes (add or remove).
onReject (rejections: FileRejection[]) => void - Fired with files rejected by accept / maxSize / maxFiles.
accept string - Accepted types (an <input accept> string, e.g. image/*,.pdf).
multiple boolean false Allow selecting more than one file.
maxSize number - Max size per file, in bytes.
maxFiles number - Maximum number of files.
messages Partial<FileMessages> - Override the prompt / browse / remove / accepted-types strings.

Plus the shared editor-contract props: disabled, readonly, required, invalid, error, label, hint, dir, id, ariaLabel.

FileRejection

type FileRejectReason = 'type' | 'size' | 'count'
type FileRejection = { file: File; reason: FileRejectReason }

// messages overrides (all optional):
type FileMessages = { prompt: string; browse: string; remove: string; hintTypes: string }

Patterns

Validate and report rejections

Set the constraints and surface why a file was turned away using the reason:

<SvFileUpload {files} accept=".csv" maxFiles={3} maxSize={2_000_000}
  onChange={(f) => (files = f)}
  onReject={(r) => (msg = r.map((x) => `${x.file.name}: ${x.reason}`).join(', '))} />

Field validation

Because it uses the editor contract, wire it into a form like any input - set required, invalid, and an error string and they render through the shared field chrome:

<SvFileUpload {files} label="Resume" required invalid={!files.length}
  error={!files.length ? 'A file is required' : undefined}
  onChange={(f) => (files = f)} />

Localized strings

Override any of the prompt / browse / remove / accepted-types labels via messages for non-English UIs:

<SvFileUpload {files} messages={{ prompt: 'Fichiers ici ou', browse: 'parcourir' }} />

Accept several images, cap the count and size, and turn the reason on each rejection into readable copy:

<script lang="ts">
  import { SvFileUpload, type FileRejection } from '@svgrid/grid'
  let files = $state<File[]>([])
  let notice = $state('')
  const reasons: Record<FileRejection['reason'], string> = {
    type: 'not an image', size: 'over 4 MB', count: 'too many files',
  }
</script>

<SvFileUpload
  {files}
  label="Gallery images"
  hint="Up to 6 images, 4 MB each"
  accept="image/*"
  multiple
  maxFiles={6}
  maxSize={4 * 1024 * 1024}
  onChange={(f) => (files = f)}
  onReject={(r: FileRejection[]) =>
    (notice = r.map((x) => `${x.file.name}: ${reasons[x.reason]}`).join(', '))}
/>
{#if notice}<p>{notice}</p>{/if}

Tip: reason is one of 'type' | 'size' | 'count', so map it to your own copy per rule rather than showing a generic "rejected" message.

Accessibility

See also