← Back to Examples

✅ Validation & Rejection

Size limits, count limits, MIME/extension filters, and custom validators

VA01 · Max File Size (max-file-size)

max-file-size accepts bytes or a human suffix (5MB, 300kB, 1.5GB — binary/1024-based units). Files larger than the limit are rejected with code 'size'.

VA02 · Max File Count (max-file-count)

Once the count is reached, subsequent files are rejected with code 'count'.

Single mode replaces rather than rejects — no rejection event fires.

VA03 · Type Filter (accept)

Files that fail the MIME/extension match are rejected with code 'type'.

VA04 · Custom Validator

Set validateCallback on the element to add custom validation. Return { valid: false, error: '...' } to reject; the failure surfaces alongside built-in validation errors via the files-rejected event.

const el = document.getElementById('custom-validate')

el.validateCallback = (file, existingFiles) => {
    if (file.name.includes(' ')) {
        return { valid: false, error: 'Filename must not contain spaces' }
    }
    return { valid: true }
}

VA05 · Async Confirmation Gates

beforeFilesAddedCallback and beforeFilesRemovedCallback let you pop a dialog before files actually enter or leave the dropzone. They run AFTER the sync validators pass (size, type, count, validateCallback) and are skipped for programmatic removeFile(id) / clear() calls unless you pass { confirm: true }. Resolving false on the add gate emits files-rejected with code: 'cancelled'.

const el = document.getElementById('confirm-gate')

el.beforeFilesAddedCallback = async (files, existingFiles) => {
    const names = files.map(f => f.name).join(', ')
    return confirm(`Add ${files.length} file(s)?\n\n${names}`)
}

el.beforeFilesRemovedCallback = async (files, allFiles) => {
    if (files.length === 1) {
        return confirm(`Remove "${files[0].name}"?`)
    }
    return confirm(`Remove all ${files.length} files?`)
}

VA06 · Combined Rules

All validation rules apply together. The first failing rule wins.

VA07 · Rejection Codes

Each rejection includes a code so you can branch on it programmatically.

el.addEventListener('files-rejected', (e) => {
    for (const { file, validation } of e.detail.rejectedFiles) {
        switch (validation.code) {
            case 'size':   showError(`${file.name} is too large`);  break
            case 'type':   showError(`${file.name} type not allowed`); break
            case 'count':  showError('Too many files');              break
            case 'custom': showError(validation.error);              break
        }
    }
})