Dropzone

The brio-dropzone web component can be used for styling drag-and-drop file upload areas

Examples

Basic usage

Drag and drop a file here, or click to browse PNG, JPG, or PDF up to 10 MB
<brio-dropzone class="dropzone">
                <input type="file" id="ex-attachment" accept=".png,.jpg,.jpeg,.pdf" />
                <svg
                    xmlns="http://www.w3.org/2000/svg"
                    width="32"
                    height="32"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    aria-hidden="true"
                >
                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
                    <polyline points="17 8 12 3 7 8" />
                    <line x1="12" y1="3" x2="12" y2="15" />
                </svg>
                <span data-prompt>Drag and drop a file here, or click to browse</span>
                <span class="text-subtle">PNG, JPG, or PDF up to 10 MB</span>
            </brio-dropzone>

Native form submission

Drop a PDF or click to browse
<form
            method="post"
            enctype="multipart/form-data"
            action="#"
            onsubmit="return false;"
            class="stack"
        >
            <label class="label" for="ex-form-attachment">Attachment</label>
            <docs-resize>
                <brio-dropzone class="dropzone">
                    <input type="file" id="ex-form-attachment" name="attachment" accept=".pdf" />
                    <svg
                        xmlns="http://www.w3.org/2000/svg"
                        width="28"
                        height="28"
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="2"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        aria-hidden="true"
                    >
                        <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
                        <polyline points="17 8 12 3 7 8" />
                        <line x1="12" y1="3" x2="12" y2="15" />
                    </svg>
                    <span data-prompt>Drop a PDF or click to browse</span>
                </brio-dropzone>
            </docs-resize>
            <button type="submit" class="button is-primary">Submit</button>
        </form>

Validation

Drag and drop a file here, or click to browse
<brio-dropzone class="dropzone">
                <input
                    type="file"
                    id="ex-invalid-input"
                    accept=".png,.jpg,.pdf"
                    aria-invalid="true"
                    aria-describedby="ex-invalid-msg"
                />
                <svg
                    xmlns="http://www.w3.org/2000/svg"
                    width="28"
                    height="28"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    aria-hidden="true"
                >
                    <circle cx="12" cy="12" r="10" />
                    <line x1="12" y1="8" x2="12" y2="12" />
                    <line x1="12" y1="16" x2="12.01" y2="16" />
                </svg>
                <span data-prompt>Drag and drop a file here, or click to browse</span>
            </brio-dropzone>

An attachment is required.

States

Drop file to upload report-q1-2026.pdf File type not accepted: sketch-draft.psd
<div class="stack">
            <docs-resize>
                <brio-dropzone class="dropzone is-dragover">
                    <input type="file" id="ex-state-dragover" />
                    <svg
                        xmlns="http://www.w3.org/2000/svg"
                        width="28"
                        height="28"
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="2"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        aria-hidden="true"
                    >
                        <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
                        <polyline points="17 8 12 3 7 8" />
                        <line x1="12" y1="3" x2="12" y2="15" />
                    </svg>
                    <span data-prompt>Drop file to upload</span>
                </brio-dropzone>
            </docs-resize>
            <docs-resize>
                <brio-dropzone class="dropzone is-accepted">
                    <input type="file" id="ex-state-accepted" />
                    <svg
                        xmlns="http://www.w3.org/2000/svg"
                        width="28"
                        height="28"
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="2"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        aria-hidden="true"
                    >
                        <path d="M20 6 9 17l-5-5" />
                    </svg>
                    <span data-prompt>report-q1-2026.pdf</span>
                </brio-dropzone>
            </docs-resize>
            <docs-resize>
                <brio-dropzone class="dropzone is-rejected">
                    <input type="file" id="ex-state-rejected" />
                    <svg
                        xmlns="http://www.w3.org/2000/svg"
                        width="28"
                        height="28"
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="2"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        aria-hidden="true"
                    >
                        <circle cx="12" cy="12" r="10" />
                        <line x1="15" y1="9" x2="9" y2="15" />
                        <line x1="9" y1="9" x2="15" y2="15" />
                    </svg>
                    <span data-prompt>File type not accepted: sketch-draft.psd</span>
                </brio-dropzone>
            </docs-resize>
        </div>

Source comments

Dropzone -- drag-and-drop file upload area (extra).

Slots: none. Authored content (icon, prompt, hint) goes inside as
regular markup -- the dropzone stacks its children with a snug gap.

State (per P5 -- .is-* only when no ARIA equivalent exists):
  :focus-within                    Keyboard focus on the file input
                                   (no JS hop -- modern selector)
  :hover                           Pointer over the dropzone
  .is-dragover                     A drag operation is currently
                                   over the dropzone (set by JS)
  .is-accepted                     Files validated and adopted
                                   (set by JS, persists)
  .is-rejected                     Files rejected by the input's
                                   accept-pattern check (set by
                                   JS, cleared by the next valid
                                   drop or by the consumer)
  input[aria-invalid="true"]      Server- or JS-set invalid; paints
                                   the dropzone border in danger,
                                   same pattern as the .input family

Markup:
  <brio-dropzone class="dropzone">
      <input type="file" id="attachment" accept=".png,.jpg,.pdf">
      <svg aria-hidden="true">...</svg>
      <span data-prompt>
          Drag and drop a file here, or click to browse
      </span>
      <span class="text-subtle">PNG, JPG, or PDF up to 10 MB</span>
  </brio-dropzone>

The file input stays in the DOM (visually hidden, still focusable
for keyboard users). The custom element forwards clicks anywhere
on the dropzone to the input, opening the native file picker. The
data-prompt span is the JS update target -- the element rewrites
its textContent when files are accepted or rejected.

Archetype: Themed veneer (P3) over a custom element. The class
carries the look; the custom element carries the behavior. Without
the JS, the file input is hidden and the dropzone is non-functional
-- the styling presumes its companion JS is loaded.

Why a class on a custom element:
  Consistent with .tabs on <brio-tabs>, .input on form atoms, etc.
  The class is the "I want brio's styling" gate; the custom element
  tag is the JS gate. They pair in the canonical markup but the
  CSS doesn't reach for the tag selector -- the class is the API.

State remaps (declared inside the state rule) override only the
  color tokens; padding / gap / radius stay constant across states.

Future considerations:
  .is-image -- preview the dropped image inline (FileReader or
  object URL into a thumbnail slot). Not in initial port; revisit
  when an image-upload-heavy consumer needs it.

Reference

Source: src/css/extras/brio-dropzone.css

Applies to

.dropzone

Classes

ClassKindDescription
.is-dragovervariantActive drag state when a file is over the dropzone
.is-acceptedvariantFiles validated and adopted successfully
.is-rejectedvariantFiles rejected by the accept-pattern check

Tokens

TokenDefaultDescription
--dropzone-border-colorvar(--color-border-strong)Border color for the dropzone container
--dropzone-border-styledashedBorder style, dashed by default to signal a drop target
--dropzone-border-width2pxBorder width for the dropzone container
--dropzone-bgvar(--color-surface)Background color for the dropzone surface
--dropzone-colorvar(--color-text-muted)Text color for the dropzone prompt
--dropzone-icon-colorvar(--color-text-subtle)Color for SVG icons inside the dropzone
--dropzone-radiusvar(--radius-md)Corner radius for the dropzone
--dropzone-padding-inlinevar(--ui-pad-inline-spacious)Inline-axis padding inside the dropzone
--dropzone-padding-blockvar(--ui-pad-block-spacious)Block-axis padding inside the dropzone
--dropzone-gapvar(--ui-gutter-snug)Gap between stacked children inside the dropzone
--dropzone-font-sizevar(--ui-font-size-sm)Font size for the prompt and hint text
--dropzone-transitionborder-color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out)Transition shorthand for state color changes