> ## Documentation Index
> Fetch the complete documentation index at: https://prefab.prefect.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# DropZone

> Drag-and-drop file upload area.

export const ComponentPreview = ({json, height, resizable, bare, hideJson, playground, children}) => {
  const hostRef = React.useRef(null);
  const handleRef = React.useRef(null);
  const cardRef = React.useRef(null);
  const containerRef = React.useRef(null);
  const [previewWidth, setPreviewWidth] = React.useState(null);
  const [isDragging, setIsDragging] = React.useState(false);
  const [playgroundHref, setPlaygroundHref] = React.useState(null);
  React.useEffect(function () {
    if (!playground) return;
    var isLocal = location.hostname === "localhost" || location.hostname === "127.0.0.1";
    var path = isLocal ? "/playground" : "/docs/playground";
    var url = new URL(path, window.location.href);
    url.hash = "code=" + playground;
    setPlaygroundHref(url.href);
  }, [playground]);
  var jsonStr = typeof json === "string" ? json : JSON.stringify(json, null, 2);
  React.useEffect(function () {
    if (cardRef.current) {
      var card = cardRef.current.closest(".group");
      if (card) card.classList.remove("group");
    }
    var host = hostRef.current;
    if (!host || !json) return;
    function mount() {
      if (!window.__prefab || !host) return;
      var dark = document.documentElement.classList.contains("dark");
      handleRef.current = window.__prefab.mountPreview(host, jsonStr, {
        dark: dark
      });
      if (bare && host.shadowRoot) {
        var m = host.shadowRoot.querySelector("[data-prefab-mount]");
        if (m) m.style.background = "transparent";
      }
    }
    if (window.__prefab) {
      mount();
    } else {
      if (!window.__prefabLoading) {
        if (!window.__prefabReady) {
          var s = document.createElement("script");
          s.src = "/renderer.js";
          document.head.appendChild(s);
        }
        window.__prefabLoading = new Promise(function (resolve) {
          function check() {
            if (window.__prefabReady) {
              window.__prefabReady.then(resolve);
            } else {
              setTimeout(check, 10);
            }
          }
          check();
        });
      }
      window.__prefabLoading.then(mount);
    }
    var observer = new MutationObserver(function () {
      var dark = document.documentElement.classList.contains("dark");
      if (handleRef.current) handleRef.current.setDark(dark);
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return function () {
      observer.disconnect();
      if (handleRef.current) {
        handleRef.current.unmount();
        handleRef.current = null;
      }
    };
  }, [json]);
  function startResize(e) {
    e.preventDefault();
    var startX = e.clientX;
    var startW = hostRef.current ? hostRef.current.offsetWidth : 0;
    var maxW = containerRef.current ? containerRef.current.offsetWidth - 10 : startW;
    setIsDragging(true);
    function onMove(ev) {
      var w = Math.max(260, Math.min(startW + (ev.clientX - startX), maxW));
      setPreviewWidth(w);
    }
    function onUp() {
      document.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseup", onUp);
      setIsDragging(false);
    }
    document.addEventListener("mousemove", onMove);
    document.addEventListener("mouseup", onUp);
  }
  if (bare) {
    return <div ref={hostRef} />;
  }
  if (resizable) {
    return <Card>
        <div ref={cardRef} />
        <div ref={containerRef} style={{
      display: "flex",
      position: "relative",
      overflow: "hidden"
    }}>
          <div ref={hostRef} style={{
      flex: previewWidth ? "none" : 1,
      width: previewWidth ? previewWidth + "px" : undefined,
      minWidth: 0
    }} />
          <div onMouseDown={startResize} style={{
      width: "14px",
      flexShrink: 0,
      cursor: "col-resize",
      background: isDragging ? "var(--border, #e5e7eb)" : "color-mix(in srgb, var(--border, #e5e7eb) 40%, transparent)",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      transition: "background 0.15s",
      userSelect: "none"
    }}>
            <svg width="6" height="24" style={{
      opacity: 0.5
    }}>
              <circle cx="2" cy="4" r="1.2" fill="currentColor" />
              <circle cx="2" cy="9" r="1.2" fill="currentColor" />
              <circle cx="2" cy="14" r="1.2" fill="currentColor" />
              <circle cx="2" cy="19" r="1.2" fill="currentColor" />
              <circle cx="5" cy="4" r="1.2" fill="currentColor" />
              <circle cx="5" cy="9" r="1.2" fill="currentColor" />
              <circle cx="5" cy="14" r="1.2" fill="currentColor" />
              <circle cx="5" cy="19" r="1.2" fill="currentColor" />
            </svg>
          </div>
          <div style={{
      flex: previewWidth ? 1 : "0 0 24px",
      minWidth: "24px",
      background: "repeating-linear-gradient(-45deg, transparent, transparent 3px, var(--border, #e5e7eb) 3px, var(--border, #e5e7eb) 4px)",
      opacity: 0.4
    }} />
          {previewWidth && <div style={{
      position: "absolute",
      bottom: "8px",
      right: "8px",
      background: "var(--background, white)",
      border: "1px solid var(--border, #e5e7eb)",
      borderRadius: "4px",
      padding: "1px 6px",
      fontSize: "11px",
      fontFamily: "monospace",
      color: "var(--muted-foreground, #6b7280)",
      pointerEvents: "none"
    }}>
              {previewWidth}px
            </div>}
        </div>
        {playgroundHref && <div style={{
      display: "flex",
      justifyContent: "flex-end",
      padding: "4px 0 8px"
    }}>
            <a href={playgroundHref} target="_blank" rel="noopener noreferrer" style={{
      fontSize: "11px",
      opacity: 0.4,
      textDecoration: "none",
      textDecorationLine: "none",
      borderBottom: "none",
      boxShadow: "none",
      display: "inline-flex",
      alignItems: "center",
      gap: "3px",
      transition: "opacity 0.15s",
      fontWeight: 400
    }} onMouseOver={function (e) {
      e.currentTarget.style.opacity = "0.6";
    }} onMouseOut={function (e) {
      e.currentTarget.style.opacity = "0.4";
    }}>
              Edit in Playground
              <svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg>
            </a>
          </div>}
        <div style={{
      marginBottom: "-2rem"
    }}>{children}</div>
      </Card>;
  }
  return <Card>
      <div ref={cardRef} />
      <div ref={hostRef} />
      {playgroundHref && <div style={{
    display: "flex",
    justifyContent: "flex-end",
    padding: "4px 0 8px"
  }}>
          <a href={playgroundHref} target="_blank" rel="noopener noreferrer" style={{
    fontSize: "11px",
    opacity: 0.4,
    textDecoration: "none",
    textDecorationLine: "none",
    borderBottom: "none",
    boxShadow: "none",
    display: "inline-flex",
    alignItems: "center",
    gap: "3px",
    transition: "opacity 0.15s",
    fontWeight: 400
  }} onMouseOver={function (e) {
    e.currentTarget.style.opacity = "0.6";
  }} onMouseOut={function (e) {
    e.currentTarget.style.opacity = "0.4";
  }}>
            Edit in Playground
            <svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg>
          </a>
        </div>}
      <div style={{
    marginBottom: "-2rem"
  }}>{children}</div>
    </Card>;
};

A styled drag-and-drop area for file uploads. Users can drop files onto it or click to browse — either way, the selected files are read client-side, converted to base64, and passed through the action system as `$event`. No server round-trip until you want one.

<Info>
  All file reading happens in the browser. Files are never sent to a server unless you explicitly wire up an action (like `CallTool`) to do so.
</Info>

If you don't need a dedicated upload area — say you just want an "Upload" button — see the [OpenFilePicker](/actions/open-file-picker) action instead. It turns any clickable element into a file input.

## Basic Usage

With no configuration, DropZone renders a full-width dashed upload area that accepts a single file of any type.

<ComponentPreview json={{"view":{"name":"dropzone_1","type":"DropZone","multiple":false,"disabled":false}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgRHJvcFpvbmUKCkRyb3Bab25lKCkK">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import DropZone

    DropZone()
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {"name": "dropzone_1", "type": "DropZone", "multiple": false, "disabled": false}
    }
    ```
  </CodeGroup>
</ComponentPreview>

## Customizing the Prompt

The `label` sets the primary prompt text and `description` adds secondary helper text below it — useful for hinting at accepted formats or size limits. You can also swap the default upload icon for any [Lucide icon](https://lucide.dev/icons) via the `icon` prop.

<ComponentPreview json={{"view":{"name":"dropzone_2","type":"DropZone","icon":"image","label":"Drop images here","description":"PNG, JPG, or GIF up to 10MB","multiple":false,"disabled":false}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgRHJvcFpvbmUKCkRyb3Bab25lKAogICAgaWNvbj0iaW1hZ2UiLAogICAgbGFiZWw9IkRyb3AgaW1hZ2VzIGhlcmUiLAogICAgZGVzY3JpcHRpb249IlBORywgSlBHLCBvciBHSUYgdXAgdG8gMTBNQiIsCikK">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import DropZone

    DropZone(
        icon="image",
        label="Drop images here",
        description="PNG, JPG, or GIF up to 10MB",
    )
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "name": "dropzone_2",
        "type": "DropZone",
        "icon": "image",
        "label": "Drop images here",
        "description": "PNG, JPG, or GIF up to 10MB",
        "multiple": false,
        "disabled": false
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

## Accepted File Types

The `accept` parameter restricts which files the browser will allow. It works just like the HTML `accept` attribute — MIME types (`"image/*"`), extensions (`".csv"`), or a comma-separated mix of both.

<ComponentPreview json={{"view":{"name":"dropzone_3","type":"DropZone","label":"Upload documents","description":"PDF, DOCX, or TXT","accept":".pdf,.docx,.txt","multiple":false,"disabled":false}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgRHJvcFpvbmUKCkRyb3Bab25lKAogICAgbGFiZWw9IlVwbG9hZCBkb2N1bWVudHMiLAogICAgZGVzY3JpcHRpb249IlBERiwgRE9DWCwgb3IgVFhUIiwKICAgIGFjY2VwdD0iLnBkZiwuZG9jeCwudHh0IiwKKQo">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import DropZone

    DropZone(
        label="Upload documents",
        description="PDF, DOCX, or TXT",
        accept=".pdf,.docx,.txt",
    )
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "name": "dropzone_3",
        "type": "DropZone",
        "label": "Upload documents",
        "description": "PDF, DOCX, or TXT",
        "accept": ".pdf,.docx,.txt",
        "multiple": false,
        "disabled": false
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

## Multiple Files

By default, DropZone accepts one file. Set `multiple=True` to let users select several at once — each new drop accumulates into the state array rather than replacing it.

<ComponentPreview json={{"view":{"name":"dropzone_4","type":"DropZone","label":"Upload files","description":"Select as many as you like","multiple":true,"disabled":false}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgRHJvcFpvbmUKCkRyb3Bab25lKAogICAgbGFiZWw9IlVwbG9hZCBmaWxlcyIsCiAgICBkZXNjcmlwdGlvbj0iU2VsZWN0IGFzIG1hbnkgYXMgeW91IGxpa2UiLAogICAgbXVsdGlwbGU9VHJ1ZSwKKQo">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import DropZone

    DropZone(
        label="Upload files",
        description="Select as many as you like",
        multiple=True,
    )
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "name": "dropzone_4",
        "type": "DropZone",
        "label": "Upload files",
        "description": "Select as many as you like",
        "multiple": true,
        "disabled": false
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

## Handling Uploads

Files stay in the browser until you do something with them. The `on_change` parameter fires actions when files are selected, with `$event` containing the file data — so the typical pattern is to pair it with a `CallTool` that sends the base64 payload to your MCP server for processing.

```python DropZone with CallTool theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from prefab_ui.components import DropZone
from prefab_ui.actions.mcp import CallTool

DropZone(
    label="Upload CSV",
    accept=".csv",
    on_change=CallTool(
        "process_csv",
        arguments={"file": "{{ $event }}"},
    ),
)
```

Since `on_change` accepts a list, you can chain actions for UI feedback — show a loading state, call the tool, then clear the loading state:

```python DropZone with chained actions theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from prefab_ui.components import DropZone
from prefab_ui.actions import SetState
from prefab_ui.actions.mcp import CallTool

DropZone(
    label="Upload data file",
    on_change=[
        SetState("uploading", True),
        CallTool("upload_file", arguments={"file": "{{ $event }}"}),
        SetState("uploading", False),
    ],
)
```

## The `FileUpload` Type

Both DropZone and [OpenFilePicker](/actions/open-file-picker) always produce `$event` as `list[FileUpload]` — an array of objects with `name`, `size` (bytes), `type` (MIME), and `data` (raw base64 — no `data:` URL prefix). Even single-file uploads produce a one-element array.

Prefab ships a `FileUpload` type so you can annotate your MCP tool parameters with it:

```python Typing a tool parameter theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from prefab_ui.actions import FileUpload

@server.tool()
def process_csv(files: list[FileUpload]):
    import base64
    for f in files:
        contents = base64.b64decode(f.data)
        # f.name, f.size, f.type also available
        ...
```

## Disabled State

<ComponentPreview json={{"view":{"name":"dropzone_5","type":"DropZone","label":"Uploads disabled","multiple":false,"disabled":true}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgRHJvcFpvbmUKCkRyb3Bab25lKGxhYmVsPSJVcGxvYWRzIGRpc2FibGVkIiwgZGlzYWJsZWQ9VHJ1ZSkK">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import DropZone

    DropZone(label="Uploads disabled", disabled=True)
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "name": "dropzone_5",
        "type": "DropZone",
        "label": "Uploads disabled",
        "multiple": false,
        "disabled": true
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

## Reading the Value

DropZone stores selected file data in client state as `list[FileUpload]` — always an array, defaulting to `[]`. Each entry has `name`, `size` (bytes), `type` (MIME), and `data` (base64) properties. With `multiple=True`, files accumulate across drops; with `multiple=False`, each upload replaces the previous one.

Use `.rx` to reference file data in other components. The `.length()` pipe counts entries, and `ForEach` iterates over the array to display each file's details. Drop some files below to see it work:

<ComponentPreview json={{"view":{"cssClass":"gap-3","type":"Column","children":[{"name":"dropzone_6","type":"DropZone","label":"Drop files to try it","multiple":true,"disabled":false},{"type":"Condition","cases":[{"when":"{{ dropzone_6 | length }}","children":[{"cssClass":"gap-2","type":"Column","children":[{"cssClass":"text-sm font-medium","content":"{{ dropzone_6 | length }} file(s) selected","type":"Text"},{"let":{"_loop_2":"{{ $item }}","_loop_2_idx":"{{ $index }}"},"type":"ForEach","key":"dropzone_6","children":[{"cssClass":"gap-2 items-center","type":"Row","children":[{"type":"Badge","label":"{{ $item.type }}","variant":"secondary"},{"content":"{{ $item.name }}","type":"Small"},{"content":"{{ $item.size }} bytes","type":"Muted"}]}]}]}]}],"else":[{"content":"No files selected yet","type":"Muted"}]}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgKAogICAgQmFkZ2UsIENvbHVtbiwgRHJvcFpvbmUsIEZvckVhY2gsCiAgICBJZiwgRWxzZSwgTXV0ZWQsIFJvdywgU21hbGwsIFRleHQsCikKCndpdGggQ29sdW1uKGdhcD0zKToKICAgIHpvbmUgPSBEcm9wWm9uZSgKICAgICAgICBsYWJlbD0iRHJvcCBmaWxlcyB0byB0cnkgaXQiLAogICAgICAgIG11bHRpcGxlPVRydWUsCiAgICApCiAgICB3aXRoIElmKGYie3pvbmUucngubGVuZ3RoKCl9Iik6CiAgICAgICAgd2l0aCBDb2x1bW4oZ2FwPTIpOgogICAgICAgICAgICBUZXh0KAogICAgICAgICAgICAgICAgZiJ7em9uZS5yeC5sZW5ndGgoKX0gZmlsZShzKSBzZWxlY3RlZCIsCiAgICAgICAgICAgICAgICBjc3NfY2xhc3M9InRleHQtc20gZm9udC1tZWRpdW0iLAogICAgICAgICAgICApCiAgICAgICAgICAgIHdpdGggRm9yRWFjaCh6b25lLnJ4KToKICAgICAgICAgICAgICAgIHdpdGggUm93KGdhcD0yLCBhbGlnbj0iY2VudGVyIik6CiAgICAgICAgICAgICAgICAgICAgQmFkZ2UoInt7ICRpdGVtLnR5cGUgfX0iLCB2YXJpYW50PSJzZWNvbmRhcnkiKQogICAgICAgICAgICAgICAgICAgIFNtYWxsKCJ7eyAkaXRlbS5uYW1lIH19IikKICAgICAgICAgICAgICAgICAgICBNdXRlZCgie3sgJGl0ZW0uc2l6ZSB9fSBieXRlcyIpCiAgICB3aXRoIEVsc2UoKToKICAgICAgICBNdXRlZCgiTm8gZmlsZXMgc2VsZWN0ZWQgeWV0IikK">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import (
        Badge, Column, DropZone, ForEach,
        If, Else, Muted, Row, Small, Text,
    )

    with Column(gap=3):
        zone = DropZone(
            label="Drop files to try it",
            multiple=True,
        )
        with If(f"{zone.rx.length()}"):
            with Column(gap=2):
                Text(
                    f"{zone.rx.length()} file(s) selected",
                    css_class="text-sm font-medium",
                )
                with ForEach(zone.rx):
                    with Row(gap=2, align="center"):
                        Badge("{{ $item.type }}", variant="secondary")
                        Small("{{ $item.name }}")
                        Muted("{{ $item.size }} bytes")
        with Else():
            Muted("No files selected yet")
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "gap-3",
        "type": "Column",
        "children": [
          {
            "name": "dropzone_6",
            "type": "DropZone",
            "label": "Drop files to try it",
            "multiple": true,
            "disabled": false
          },
          {
            "type": "Condition",
            "cases": [
              {
                "when": "{{ dropzone_6 | length }}",
                "children": [
                  {
                    "cssClass": "gap-2",
                    "type": "Column",
                    "children": [
                      {
                        "cssClass": "text-sm font-medium",
                        "content": "{{ dropzone_6 | length }} file(s) selected",
                        "type": "Text"
                      },
                      {
                        "let": {"_loop_2": "{{ $item }}", "_loop_2_idx": "{{ $index }}"},
                        "type": "ForEach",
                        "key": "dropzone_6",
                        "children": [
                          {
                            "cssClass": "gap-2 items-center",
                            "type": "Row",
                            "children": [
                              {"type": "Badge", "label": "{{ $item.type }}", "variant": "secondary"},
                              {"content": "{{ $item.name }}", "type": "Small"},
                              {"content": "{{ $item.size }} bytes", "type": "Muted"}
                            ]
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            ],
            "else": [{"content": "No files selected yet", "type": "Muted"}]
          }
        ]
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

Inside `ForEach`, the template variables `$item.name`, `$item.type`, and `$item.size` resolve to each file's properties.

## API Reference

<Card icon="code" title="DropZone Parameters">
  <ParamField body="icon" type="str | None" default="None">
    Lucide icon name in kebab-case (e.g. `"cloud-upload"`, `"image"`). Defaults to an upload icon.
  </ParamField>

  <ParamField body="label" type="str | None" default="None">
    Primary prompt text shown in the drop zone.
  </ParamField>

  <ParamField body="description" type="str | None" default="None">
    Secondary helper text below the label (e.g. file type hints).
  </ParamField>

  <ParamField body="accept" type="str | None" default="None">
    File type filter. Accepts MIME types (`"image/*"`), extensions (`".csv"`), or a comma-separated mix (`".pdf,.docx,.txt"`).
  </ParamField>

  <ParamField body="multiple" type="bool" default="False">
    Allow selecting multiple files. When `True`, files accumulate across drops. When `False`, each upload overwrites the previous one.
  </ParamField>

  <ParamField body="max_size" type="int | None" default="None">
    Maximum file size in bytes per file. Files exceeding this are rejected with a toast notification.
  </ParamField>

  <ParamField body="disabled" type="bool" default="False">
    Whether the drop zone is non-interactive.
  </ParamField>

  <ParamField body="name" type="str | None" default="None">
    State key for auto-state binding. When set without `on_change`, file data is stored in state under this key.
  </ParamField>

  <ParamField body="on_change" type="Action | list[Action] | None" default="None">
    Action(s) to execute when files are selected. `$event` is always `list[FileUpload]`.
  </ParamField>

  <ParamField body="css_class" type="str | None" default="None">
    Additional Tailwind CSS classes appended to the component's built-in styles.
  </ParamField>
</Card>

## Protocol Reference

```json DropZone theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
{
  "type": "DropZone",
  "id?": "string",
  "onMount?": "any",
  "name?": "string",
  "value?": "any[]",
  "icon?": "string",
  "label?": "string",
  "description?": "string",
  "accept?": "string",
  "multiple?": false,
  "maxSize?": "number",
  "disabled?": false,
  "onChange?": "Action | Action[]",
  "cssClass?": "string"
}
```

For the complete protocol schema, see [DropZone](/protocol/drop-zone).
