Styled file input with drag-and-drop support, built on the @foldkit/ui FileDrop submodel.
Add this component to your project:
npx shadcn@latest add @foldcn/file-dropThe component ships as plain source — no build step, no wrapper. Copy it and make it yours.
registry/default/ui/file-drop.ts64 linesimport { FileDrop as FoldkitFileDrop } from '@foldkit/ui'
import type { Html, HtmlBuilder } from 'foldkit/html'
type Child = Html | string
import { cn } from '@/lib/utils'
// Re-export the @foldkit/ui FileDrop submodel surface.
export const init = FoldkitFileDrop.init
export const update = FoldkitFileDrop.update
export const view = FoldkitFileDrop.view
export const Model = FoldkitFileDrop.Model
export type Model = typeof Model.Type
export const Message = FoldkitFileDrop.Message
export type Message = typeof Message.Type
export const OutMessage = FoldkitFileDrop.OutMessage
export type OutMessage = typeof OutMessage.Type
export type InitConfig = FoldkitFileDrop.InitConfig
export type ViewInputs = FoldkitFileDrop.ViewInputs
export type FileDropAttributes = FoldkitFileDrop.FileDropAttributes
export const fileDropClass =
'flex cursor-pointer flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed border-border bg-card px-6 py-10 text-center text-card-foreground transition-colors hover:border-primary/50 data-[drag-over]:border-primary data-[drag-over]:bg-accent data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50'
export const fileDropPrimaryTextClass = 'text-base font-medium'
export const fileDropSecondaryTextClass = 'text-sm text-muted-foreground'
export const fileRowClass =
'flex items-center justify-between gap-3 rounded-lg border border-border bg-card px-3 py-2'
export const fileNameClass = 'truncate text-sm font-medium'
export const fileSizeClass = 'text-xs text-muted-foreground'
export const fileRemoveButtonClass =
'text-sm text-muted-foreground transition-colors hover:text-destructive cursor-pointer'
export type StyledViewInputs = Readonly<{
multiple?: boolean
isDisabled?: boolean
accept?: ReadonlyArray<string>
/** Drop zone content (hint text, etc.). */
content: ReadonlyArray<Child>
className?: string
}>
/** Build styled `FileDrop.ViewInputs`. Pass your view's `h`. */
export const styledViewInputs = <M>(
viewInputs: StyledViewInputs,
h: HtmlBuilder<M>,
): ViewInputs => ({
multiple: viewInputs.multiple,
isDisabled: viewInputs.isDisabled,
accept: viewInputs.accept,
toView: (attributes) =>
h.label(
[...attributes.root, h.Class(cn(fileDropClass, viewInputs.className))],
[...viewInputs.content, h.input(attributes.input)],
),
})