Navigation

v0.0.4
Forms

Dropzone

A wide target for choosing or dropping files.

On this page

A dropzone is a wide target for choosing or dropping files. It is a <label> around a native file input that covers it invisibly. A click opens the picker, a file dropped anywhere on it lands in the input, and the form submits it, all without script. A dashed contour says "put it here".

Dropzone

<label class="dropzone">  <input type="file" class="dropzone-input" name="attachments" multiple accept="image/*,.pdf">  <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">    <path d="M12 16V4M7 9l5-5 5 5M5 20h14" />  </svg>  <span class="dropzone-title">Drop files here or choose them</span>  <span class="dropzone-description">PNG, JPG or PDF, up to 10 MB each</span></label>

#Disabled

A disabled input drops the zone to disabled ink and stops it answering the pointer.

Disabled

<label class="dropzone">  <input type="file" class="dropzone-input" disabled>  <span class="dropzone-title">Uploads are paused</span>  <span class="dropzone-description">The project has reached its storage limit.</span></label>

#What it does not do

The browser shows the chosen files nowhere in the zone, because the input is invisible, and CSS has no state for a file being dragged over an element. List the chosen files and highlight the zone during a drag from a small script when the form needs them. For one file in a row of fields, a plain file input shows its own file name and needs nothing more.

#Accessibility

  • The input is the control: it takes focus, and the zone draws the ring around itself with :has(.dropzone-input:focus-visible).
  • The title and description are the input's label. Say the limits a reader must meet, such as types and sizes, in the description.

#Classes

ClassDescription
dropzoneThe zone, on a label: a well with a dashed contour. It draws the focus ring for its input
dropzone-inputThe file input. It fills the zone invisibly, so files dropped anywhere on the zone are taken
dropzone-titleWhat to do, in strong ink
dropzone-descriptionThe limits a reader must meet, such as types and sizes

#Source

@master/ui/components/dropzone.css
/* * Dropzone: a wide target for choosing or dropping files. * It is a <label> around a native file input that covers it invisibly, so a click opens the picker, a dropped * file lands in the input, and the form submits it, all without script. A dashed contour says "put it here". */@layer components {  .dropzone {    --icon-size: 1.25rem;    --icon-color: var(--color-text-muted);    position: relative;    display: flex;    flex-direction: column;    align-items: center;    gap: .25rem;    min-width: 0;    padding: 2rem 1.5rem;    border: 1px dashed var(--color-line-strong);    border-radius: calc(var(--radius-control-lg) * var(--corner-scale));    corner-shape: var(--corner-shape);    background: var(--color-surface-inset);    color: var(--color-text-muted);    font-size: .8125rem;    line-height: 1.5;    text-align: center;    cursor: pointer;    -webkit-tap-highlight-color: transparent;    transition: background-color var(--duration-quick) var(--easing-glide), border-color var(--duration-quick) var(--easing-glide);    @media (hover: hover) {      &:hover:not(:has(.dropzone-input:disabled)) {        border-color: var(--color-text-muted);        background: var(--color-surface-inset-hover);      }    }    /* Pressed: the edge takes the strong ink, so a tap is answered where there is no pointer. */    &:active:not(:has(.dropzone-input:disabled)) {      border-color: var(--color-text-strong);      background: var(--color-surface-inset-hover);    }    &:has(.dropzone-input:focus-visible) {      @apply --focus-ring;    }    &:has(.dropzone-input:disabled) {      border-color: var(--color-line-strong);      color: var(--color-text-disabled);      cursor: not-allowed;    }    @media (forced-colors: active) {      border-color: CanvasText;    }  }  /* The file input: it fills the zone, so files dropped anywhere on it are taken. */  .dropzone-input {    position: absolute;    inset: 0;    width: 100%;    height: 100%;    margin: 0;    padding: 0;    border: 0;    border-radius: inherit;    opacity: 0;    cursor: inherit;  }  .dropzone-title {    margin-top: .375rem;    color: var(--color-text-strong);    font-size: .875rem;    font-weight: var(--font-weight-control);    text-wrap: balance;  }  .dropzone:has(.dropzone-input:disabled) .dropzone-title {    color: inherit;  }  .dropzone-description {    text-wrap: pretty;    overflow-wrap: anywhere;  }}