For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to any URL or sending an Accept: text/markdown header. The dithercn agent skill, for building any dither on the engine, is at /.well-known/agent-skills/dithercn/SKILL.md.
dithercn
GitHub

Dropzone

A file drop area with a dithered dashed border that marches round its edge, thickens and pulls a glow in on drag-over, and turns red for files it can't take.

Controls
State
Colour
Matrix
Bloom
Cell2px
Disabled

Installation

pnpm dlx shadcn@latest add https://dithercn.dev/r/dropzone.json

Usage

import { Dropzone } from "@/components/dithercn/dropzone";
 
export function Attachments() {
  return (
    <Dropzone
      accept="image/*,application/pdf"
      maxSize={10 * 1024 * 1024}
      maxFiles={5}
      onFilesChange={({ accepted }) => upload(accepted)}
    />
  );
}

Drag files over the zone, click it, or focus it and press Enter or Space to open the file picker. Each drop or pick is checked against accept, maxSize and maxFiles, and onFilesChange gets both lists: the accepted File objects, and the rejected ones with a reason ("file-type", "file-too-large" or "too-many-files") and a short message. The zone keeps no list of its own, so you decide what to keep, upload or show.

The border is a row of dithered dashes, each a solid head with a tail that thins through the Bayer levels, marching slowly clockwise round the edge. With files over the zone the dashes lengthen and speed up, a second ring of cells fills in, and a glow is pulled in from the edges, its front moving inward. The sheet in the illustration drops into the tray in whole-cell steps. While a drag is over it, the zone can see how many files it carries and their types, so a drag it would refuse turns red, stops marching and shakes the sheet. Sizes are only known on drop, so a file that is too large turns the zone red after the drop and names the file. A rejection stays until the next drag or pick, so a long filename can be read in full.

Without a description, the line under the title is written from the rules, such as "Images or PDF, up to 10 MB each, 5 files at most". Hover and keyboard focus light the border a little, so the zone reads as something you can act on.

File list

DropzoneFileList shows files with a progress bar each: the zone's colour while uploading, solid green when done, hatched red with the message when it failed. Leave progress as null for an upload whose size you can't measure, and the bar becomes indeterminate.

import { DropzoneFileList } from "@/components/dithercn/dropzone";
 
<DropzoneFileList
  files={[
    { id: "1", name: "brand-guide.pdf", size: 3_870_000, status: "done" },
    {
      id: "2",
      name: "reel.mp4",
      size: 41_200_000,
      status: "uploading",
      progress: 38,
    },
  ]}
  onRemove={(id) => remove(id)}
/>;

Examples

Profile photo

One image at a time (multiple={false}), PNG, JPG or WEBP up to 2 MB. preview replaces the illustration: a generative avatar until a photo is chosen, then the photo itself, with its name as the title.

Uploads with progress

Up to 8 files of 50 MB each, in blue. Accepted files join the list and upload on a timer; rejected ones join it too, in red, with the reason. Drop a few files to watch them fill.

Props

PropTypeDefault
accept

File types, as the accept attribute of a file input: MIME types, wildcards and extensions, such as "image/*,.pdf". Default: any.

Type: stringDefault โ€“
maxSize

Largest file in bytes. Default: no limit.

Type: numberDefault โ€“
maxFiles

Most files per drop or pick. A drop with more is turned away whole, each file with too-many-files. Default: no limit.

Type: numberDefault โ€“
multiple

Take more than one file at a time. False means one.

Type: booleanDefault true
disabled

Turn the zone off: no picker, no drops, out of the tab order and greyed out.

Type: booleanDefault false
onFilesChange

Called after every drop or pick. A rejection is { file, reason, message }.

Type: (files: { accepted: File[]; rejected: DropzoneRejection[] }) => voidDefault โ€“
title

The heading at rest. "Drop a file here" when the zone takes one.

Type: ReactNodeDefault "Drop files here"
description

The line under the title. Replaced by the reason while a drag is refused.

Type: ReactNodeDefault From the rules
browseLabel

Text of the browse button.

Type: stringDefault "Browse files"
preview

Shown in place of the illustration, such as the chosen image.

Type: ReactNodeDefault โ€“
name

Name of a hidden file input that holds the accepted files from the last drop or pick, so a surrounding form submits them.

Type: stringDefault โ€“
color

Colour of the border, glow and illustration. Rejects are always red.

Type: DitherColorDefault "ink"
matrix

The threshold pattern.

Type: "bayer2" | "bayer4" | "bayer8" | "blue-noise"Default "bayer4"
cell

Size of one dither cell of the border in CSS pixels.

Type: numberDefault 2
radius

Corner radius in pixels. The border follows it.

Type: numberDefault 16
bloom

Soft glow round the border in its colour.

Type: "off" | "low" | "high"Default "low"
animate

March the border and bob the illustration. Drag-over still changes the look when off.

Type: booleanDefault true
forceState

Show the drag-over or refused look without a drag, for docs and design reviews.

Type: "active" | "reject"Default โ€“

DropzoneFileList takes files (each { id, name, size } plus status: "uploading" with progress from 0 to 100 or null, status: "done", or status: "error" with a message), onRemove, color and fill.

Notes

  • The zone is one control, a native <button> in the tab order, named by its title and the browse label and described by the hint. Enter and Space open the picker. The real <input type="file"> sits next to it, hidden from view and from assistive technology, and is emptied after every pick so choosing the same file again still counts. With name, a second hidden input holds the accepted files from the last drop or pick for the form.
  • A polite live region announces every result, such as "Added 3 files: a.png, b.png, c.pdf." or "Did not add report.exe: only images or PDF."
  • The border is a canvas drawn one pixel per cell and scaled up without smoothing, with no WebGL. It pauses offscreen and in hidden tabs. With reduced motion the dashes hold still, the drag-over and reject looks switch in one step, and the illustration stops bobbing and shaking. Before the first frame it shows a plain dashed edge.
  • The file list is a list of progress bars, each with role="progressbar", and every remove button is labelled with the file's name.