DropFiles

Collect local files through a picker, drag and drop, or paste, with removable selections and file-type filtering.

DropFiles

View source · API reference

Collect project attachments

Add local text, image or PDF files, remove individual entries and inspect the total size. Nothing is uploaded. In single-file mode, the next accepted file replaces the selection.

<RegorApp id="drop-files-demo" src="./playground.ts"/>
import {
  type DropFileItem,
  defineButtonComponents,
  defineDropFilesComponents,
  defineFlexComponents,
  defineFormComponents,
  defineIconComponents,
} from '@purestack/ts-components'
import {
  lucide_cloud_upload,
  lucide_file,
  lucide_file_archive,
  lucide_file_image,
  lucide_file_text,
  lucide_trash_2,
} from '@purestack/ts-svg-icons'
import {
  type ComputedRef,
  computed,
  createApp,
  defineComponent,
  html,
  type Ref,
  ref,
  type SRef,
  sref,
} from 'regor'

export interface DropFilesExample {
  selectedFiles: SRef<DropFileItem[]>
  allowMany: Ref<boolean>
  locked: Ref<boolean>
  totalBytes: ComputedRef<number>
  clear: () => void
}

const dropFilesExampleTemplate = html`<Flex direction="column">
  <DropFiles
    label="Project attachments"
    hint="Text, images and PDF files. Files stay in this browser."
    accept="text/*,image/*,.pdf"
    :files="selectedFiles"
    :multiple="allowMany"
    :disabled="locked"
  />
  <Flex wrap="true">
    <FormCheck
      id="files-multiple"
      label="Allow multiple files"
      :checked="allowMany"
    />
    <FormCheck id="files-disabled" label="Lock file selection" :checked="locked" />
    <Btn variant="link" :disabled="locked" @click="clear">Clear files</Btn>
  </Flex>
  <FormStatus>
    {{ selectedFiles.length }} files · {{ totalBytes }} bytes selected
  </FormStatus>
</Flex>`

function createDropFilesExample(): DropFilesExample {
  const selectedFiles = sref<DropFileItem[]>([])
  return {
    selectedFiles,
    allowMany: ref(true),
    locked: ref(false),
    totalBytes: computed(() =>
      selectedFiles().reduce((sum, item) => sum + item.size, 0),
    ),
    clear: () => selectedFiles([]),
  }
}

const component = defineComponent<DropFilesExample>(dropFilesExampleTemplate, {
  context: createDropFilesExample,
})
const icons: Record<string, string> = {
  'lucide:cloud-upload': lucide_cloud_upload,
  'lucide:file': lucide_file,
  'lucide:trash-2': lucide_trash_2,
  'lucide:file-image': lucide_file_image,
  'lucide:file-text': lucide_file_text,
  'lucide:file-archive': lucide_file_archive,
}

createApp(
  {
    components: {
      DropFilesExample: component,

      ...defineDropFilesComponents(),
      ...defineFlexComponents(),
      ...defineFormComponents(),
      ...defineButtonComponents(),
      ...defineIconComponents((name) => icons[name] ?? ''),
    },
  },
  { selector: 'app#drop-files-demo', template: html`<DropFilesExample />` },
)

A clearly disabled drop zone

Use a hint to explain why selection is unavailable.

Attachments
Attachments are locked Finish the current review to add files. Read-only example
<DropFiles
  label="Attachments"
  emptyTitle="Attachments are locked"
  emptyText="Finish the current review to add files."
  hint="Read-only example"
  :disabled="true"
/>

Behavior and accessibility

The drop zone opens the native picker with Enter or Space. File acceptance is a client-side convenience, not server validation. Duplicates are identified from file metadata. onChange runs when the component changes the selection; assigning the files ref yourself is application state management.

API reference

DropFiles contract

Props below are the public template API. RefOrValue accepts a literal or a reactive ref; bind refs with a colon-prefixed attribute. Native attributes and events can be passed through the component root.

files

SRef<DropFileItem[]>
Default
sref([])

Writable shallow ref of DropFileItem[]. Each item retains its File plus id, name, type, size, sizeLabel, lastModified and icon.

accept

RefOrValue<string> | string[]
Default
all files, or iconMap keys

Comma-separated MIME types, wildcards or extensions; arrays are also accepted. An explicit accept list takes precedence over iconMap keys.

iconMap

DropFilesIconMap | SRef<DropFilesIconMap>
Default
built-in type icons

MIME/extension-to-icon map merged with the defaults. When accept is omitted, a supplied map also determines accepted types.

label

RefOrValue<string>
Default
not set

Visible label. Use language that describes the control or value in its surrounding context.

hint

RefOrValue<string>
Default
not set

Supporting text beneath the drop-zone instructions.

emptyTitle

RefOrValue<string>
Default
Add files

Drop-zone heading, shown even when files are selected.

emptyText

RefOrValue<string>
Default
Click, tap, paste, or drag files here.

Drop-zone instructions.

disabled

RefOrValue<boolean>
Default
false

Disables interaction. Use a bound boolean, for example :disabled="isLocked".

multiple

RefOrValue<boolean>
Default
true

false accepts the first matching file and replaces the current list.

tone

RefOrValue<SemanticTone>
Default
inherited

Semantic intent: neutral, accent, secondary, info, success, warning, danger, feature, custom or ghost. The active skin supplies the colors.

variant

RefOrValue<ComponentVariant>
Default
surfaceAlt

Visual treatment. Accepts solid, surface, surfaceAlt, spotlight, glass, flat, flatAlt, flatSolid, outlineFill, outline, subtle, subtleBtn, link, sheen, underline, rail, bracket or none.

uploadIcon

RefOrValue<string>
Default
lucide:cloud-upload

Drop-zone icon.

fileIcon

RefOrValue<string>
Default
lucide:file

Fallback file icon when no type-specific mapping matches.

removeIcon

RefOrValue<string>
Default
lucide:trash-2

Icon on each remove button.

onChange

(files: DropFileItem[]) => void
Default
not set

Callback receiving the updated DropFileItem[] after a component-driven change.

Composition

No slots. Register defineDropFilesComponents and its icons. Use addDropFiles when another input path needs the same filtering, metadata and deduplication behavior.