Skip to content

cv-dropzone

Controlled visual shell for drag/drop or upload drop areas.

cv-dropzone does not inspect DragEvent, DataTransfer, file lists, or upload services. Consumers own all drag state and upload behavior.

Usage

View source
html
<div class="dropzone-demo-shell" data-demo="dropzone" data-live-demo-height="820">
  <section class="dropzone-demo-hero" aria-labelledby="dropzone-demo-title">
    <div class="dropzone-demo-copy">
      <span class="dropzone-demo-kicker">Controlled drop boundary</span>
      <h3 id="dropzone-demo-title">Dropzone makes drag state visible without owning upload logic.</h3>
      <p>
        Route models own drag events, validation, encryption, and upload queues. The component owns only the
        stable shell, active overlay, loading overlay, and disabled semantics.
      </p>
    </div>

    <dl class="dropzone-demo-metrics" aria-label="Dropzone contract summary">
      <div>
        <dt>State source</dt>
        <dd>consumer model</dd>
      </div>
      <div>
        <dt>Visual states</dt>
        <dd>idle / active / loading / disabled</dd>
      </div>
      <div>
        <dt>Events</dt>
        <dd>none</dd>
      </div>
    </dl>
  </section>

  <section class="dropzone-demo-workbench" aria-labelledby="dropzone-demo-workbench-title">
    <div class="dropzone-demo-section-header">
      <span class="dropzone-demo-kicker">Vault workspace</span>
      <h4 id="dropzone-demo-workbench-title">
        Place the shell around real product content so the drop boundary reads as a temporary state, not a
        separate upload page.
      </h4>
    </div>

    <div class="dropzone-demo-product" aria-label="Dropzone around a visible vault file list">
      <header class="dropzone-demo-product-header">
        <div>
          <span class="dropzone-demo-label">Visible route</span>
          <strong>travel-profile.files</strong>
        </div>
        <cv-badge variant="primary" size="small">drop target armed</cv-badge>
      </header>

      <div class="dropzone-demo-layout">
        <cv-dropzone class="dropzone-demo-main-zone" active>
          <div class="dropzone-demo-file-list" aria-label="Files visible beneath the dropzone">
            <article>
              <span class="dropzone-demo-file-mark" aria-hidden="true"></span>
              <div>
                <strong>border-entry-checklist.pdf</strong>
                <small>1.4 MB / visible namespace</small>
              </div>
              <cv-badge variant="success" size="small">synced</cv-badge>
            </article>
            <article>
              <span class="dropzone-demo-file-mark" aria-hidden="true"></span>
              <div>
                <strong>source-contact-notes.txt</strong>
                <small>18 KB / local only</small>
              </div>
              <cv-badge variant="warning" size="small">pending</cv-badge>
            </article>
            <article>
              <span class="dropzone-demo-file-mark" aria-hidden="true"></span>
              <div>
                <strong>device-route-proof.json</strong>
                <small>42 KB / audit trail</small>
              </div>
              <cv-badge variant="neutral" size="small">sealed</cv-badge>
            </article>
          </div>

          <div slot="message" class="dropzone-demo-overlay">
            <span class="dropzone-demo-target-glyph" aria-hidden="true"></span>
            <strong>Release encrypted bundle</strong>
            <small>Files are staged in this visible route. Validation and commit stay app-owned.</small>
          </div>
        </cv-dropzone>

        <aside class="dropzone-demo-pipeline" aria-label="Upload pipeline">
          <span class="dropzone-demo-kicker">Upload pipeline</span>
          <ol>
            <li>
              <strong>Inspect payload</strong>
              <span>Read file metadata from the app-owned drag event.</span>
            </li>
            <li>
              <strong>Encrypt locally</strong>
              <span>Keep plaintext out of transport and relay layers.</span>
            </li>
            <li>
              <strong>Commit visible route</strong>
              <span>Write only after the operator confirms the target.</span>
            </li>
          </ol>
          <cv-button preset="action-primary-subtle" size="small">Open file picker</cv-button>
        </aside>
      </div>
    </div>
  </section>

  <section class="dropzone-demo-section" aria-labelledby="dropzone-demo-states-title">
    <div class="dropzone-demo-section-header">
      <span class="dropzone-demo-kicker">State matrix</span>
      <h4 id="dropzone-demo-states-title">
        The same primitive covers idle, active, loading, and disabled states without taking ownership of
        drag/drop behavior.
      </h4>
    </div>

    <div class="dropzone-demo-state-grid" aria-label="Dropzone visual state examples">
      <article class="dropzone-demo-state-card">
        <span class="dropzone-demo-label">Idle</span>
        <cv-dropzone class="dropzone-demo-state-zone" message="Drop files here">
          <div class="dropzone-demo-target">
            <span class="dropzone-demo-target-glyph" aria-hidden="true"></span>
            <strong>Ready for staged files</strong>
            <p>Underlying content stays visible until the route model marks drag active.</p>
          </div>
        </cv-dropzone>
      </article>

      <article class="dropzone-demo-state-card">
        <span class="dropzone-demo-label">Active</span>
        <cv-dropzone class="dropzone-demo-state-zone" active>
          <div class="dropzone-demo-target">
            <strong>Current file list</strong>
            <p>This content is intentionally de-emphasized while the drop overlay is visible.</p>
          </div>
          <div slot="message" class="dropzone-demo-state-message">
            <strong>Release to stage</strong>
            <small>Import is still pending confirmation.</small>
          </div>
        </cv-dropzone>
      </article>

      <article class="dropzone-demo-state-card">
        <span class="dropzone-demo-label">Loading</span>
        <cv-dropzone class="dropzone-demo-state-zone" loading loading-label="Encrypting bundle">
          <div class="dropzone-demo-target">
            <strong>Upload queue locked</strong>
            <p>The shell keeps dimensions stable while processing is active.</p>
          </div>
        </cv-dropzone>
      </article>

      <article class="dropzone-demo-state-card">
        <span class="dropzone-demo-label">Disabled</span>
        <cv-dropzone class="dropzone-demo-state-zone" disabled message="Storage unavailable">
          <div class="dropzone-demo-target">
            <strong>Storage unavailable</strong>
            <p>Disabled semantics are visible without inventing an app-level fallback.</p>
          </div>
        </cv-dropzone>
      </article>
    </div>
  </section>
</div>

Anatomy

<cv-dropzone> (host)
└── <div part="base" aria-busy="false">
    ├── <div part="content">
    │   └── <slot>
    ├── <div part="overlay">
    │   └── <slot name="message">
    └── <div part="loading-overlay" role="status"> optional
        └── <slot name="loading">

Attributes

AttributeTypeDefaultDescription
activeBooleanfalseShows active drop overlay
loadingBooleanfalseShows loading overlay and sets aria-busy
disabledBooleanfalseSets disabled semantics for the shell
messageString""Fallback active overlay message
loading-labelString"Loading"Fallback loading label

Slots

SlotDescription
(default)Underlying page/list/content
messageActive overlay content
loadingLoading overlay content

CSS Parts

PartDescription
baseRoot shell
contentDefault content wrapper
overlayActive drop overlay
loading-overlayLoading overlay

Events

None. Native drag/drop and upload events remain app-owned.

ChromVoid UIKit documentation