โ† Back to Examples

๐ŸŽ›๏ธ Display & Composition

Selector & list appearance axes, card sizes, popover, files-inside, single vs multiple, accept patterns

DC01 ยท Display: three orthogonal axes

The visual surface is split into three independent attributes you can combine freely:

The legacy single-axis display-mode attribute is still supported as a shorthand โ€” see the bottom of this page.

DC02 ยท Composable controls (controls + item-controls)

Two Plyr-style allowlists. controls picks which top-level surfaces render; item-controls picks which parts of each file row render. Both are comma-separated; when an attribute is absent everything renders. Toggle the boxes and watch the dropzone recompose live โ€” add a file to see the progress parts animate.

Live:
controls โ€” surfaces
item-controls โ€” row parts
Unchecking every box in a group is the same as removing the attribute โ€” the default (render all) applies. Count- or state-based progress rules aren't extra attributes โ€” compose them from these primitives; see the recipes below.

DC03 ยท Recipes โ€” progress rules in userland

Count- or state-based progress behaviour doesn't need dedicated attributes โ€” compose it from the primitives above plus the change event and auto-upload. Two common asks:

Per-row bars only at 4+ files (count-based)

1โ€“3 files โ†’ global strip only. 4+ โ†’ both bars. The global strip is untouched; only the per-row progress token toggles.
const dz = document.getElementById('recipe-count')
const LEAN = 'icon,name,size,type,status,action,remove' // no progress

dz.addEventListener('change', () => {
  const want = dz.files.length >= 4 ? null : LEAN
  // skip no-op writes; `change` fires on selection changes only, so no loop
  if (dz.getAttribute('item-controls') === want) return
  want === null ? dz.removeAttribute('item-controls')
                : dz.setAttribute('item-controls', want)
})

Global strip only after upload starts (state-based)

With auto-upload="false" files sit pending and the strip stays hidden (hasActivity is false) until you kick off the upload โ€” no extra config, it's the default gate.
<web-dropzone auto-upload="false" ...></web-dropzone>

// files added โ†’ pending โ†’ global strip hidden (hasActivity = false)
startBtn.addEventListener('click', () => dz.uploadAll())
// upload starts โ†’ strip appears on its own

DC04 ยท Selector Appearance (selector-appearance)

Four visual entry points for picking files.

Click anywhere on the card to browse, or drag-and-drop files.
Use when the picker should sit inline (toolbars, dense forms).
Compact 36ร—36 button. The badge appears when files are picked.
Mirrors the modern browser look. Empty: "No file chosen" ยท 1 file: filename ยท multiple: "N files chosen". Customize via select-files-text and no-file-chosen-text.
The native label shows a summary; the full list still renders below per list-appearance.
Both labels are i18n-friendly.

DC05 ยท List Appearance (list-appearance)

How the picked files are presented. Six options ranging from a simple vertical list to badge-style pills or a hidden popover.

Same surface as web-multiselect badges. Default: file-type icons in the pill. Set show-thumbnails for image previews, or override --dz-badge-icon-size: 0 to hide the slot entirely.
Popover header shows file count + Add more / Clear all, with a limits hint underneath. Footer pins total size.
Picked: (none)
Submit blocked by native form validity until at least 2 files โ‰ฅ 1 KB are selected.

DC06 ยท Grid tuning โ€” live controls

Grid mode is almost entirely CSS-variable driven. Drop a few images below, then move the sliders to reshape the grid in real time. The panel writes the same --dz-* custom properties you'd set in your own CSS โ€” the live readout is copy-pasteable.

Switch grid-layout to natural for a justified photo gallery: mixed portrait/landscape images share one row height (--dz-preview-row-height) and keep their own aspect ratio โ€” nothing cropped or stretched. In uniform layout, object-fit chooses crop (cover) vs. fit (contain). Set grid-status="overlay" and let a file finish uploading to see the light veil + big centred check/error glyph.

No images handy? Any file works โ€” non-images fall back to a type icon tile, the geometry still reshapes.
columns
mode
min tile 120px
columns 4
tile
gap 12px
aspect-ratio
border-radius 6px
layout & fit
grid-layout
object-fit
row-height 160px
grid-status
count & height
max-visible-files 24
list-max-height none

DC07 ยท Card Size (card-size)

Density variants for the card selector. Only meaningful when selector-appearance="card".

DC08 ยท Files-Inside Layout (files-inside)

Render the file list inside the dropzone area instead of below it. Useful when vertical space is constrained or when you want a unified container look.

DC09 ยท Common combinations

Mixing the three axes covers the patterns users actually want โ€” toolbars, attachment fields, marketing landings, and more.

Slack/Gmail-style attachment pattern.
Click icon โ†’ opens picker. After files exist โ†’ click toggles popover.

DC10 ยท Summary Template (summary-template)

When list-appearance="popover" the summary line supports {count} and {size} placeholders โ€” quick customization without writing JS. For richer summaries (pluralization, badges) use renderSummaryCallback; see Structural Rendering.

DC11 ยท Popover Placement (popover-placement)

Positioning uses Floating UI, so it auto-flips on viewport collisions. The attribute sets the preferred initial placement.

DC12 ยท Legacy display-mode shorthand

The original single-axis attribute still works as a shorthand. When both are set, the orthogonal attribute wins.

DC13 ยท Single vs Multiple

The multiple attribute defaults to true. Set multiple="false" to lock to a single file (subsequent drops replace the existing selection).

DC14 ยท Accept Patterns

The accept attribute uses the same syntax as the native <input type="file">: comma-separated MIME types, MIME wildcards, or file extensions.

Tip

Files that don't match accept are rejected at validation time and surface via the files-rejected event โ€” see Validation.

DC15 ยท Disabled State

The disabled attribute follows HTML convention: presence enables it, ="false" overrides.

DC16 ยท Programmatic API

Methods and events let you drive the dropzone from your own code.

Events:
(none yet โ€” drop or pick a file)
const el = document.getElementById('api-demo')

el.addEventListener('file-added',     e => console.log('added',    e.detail))
el.addEventListener('file-removed',   e => console.log('removed',  e.detail))
el.addEventListener('files-rejected', e => console.log('rejected', e.detail))
el.addEventListener('change',         e => console.log('change',   e.detail))

el.addFiles(fileList)        // programmatic add (FileList or File[])
el.removeFile(id)            // remove by FileState.id
el.clear()                   // remove all
const files = el.files       // current FileState[]

DC17 ยท Custom file-type icons (fileIcons + fileIconCallback)

Each row's icon resolves from the file's category by default (Lucide glyphs). Override per extension or per category with the fileIcons map, or take full control with fileIconCallback. Values are any HTML string โ€” this demo mixes an inline SVG (.psd), an <img> (.fig), and emoji (.sketch, the archive category, and the callback).

hero.psd โ†’ SVG ยท ui.fig โ†’ <img> ยท logo.sketch โ†’ ๐Ÿ’Ž ยท assets.zip โ†’ ๐Ÿ“ฆ (archive category) ยท invoice-*.pdf โ†’ ๐Ÿงพ (callback) ยท photo.jpg / notes.txt โ†’ built-in Lucide.

โš ๏ธ Icon values are inserted as raw HTML (innerHTML) and are NOT sanitized. Only pass markup you control โ€” never untrusted / user-supplied strings.

const el = document.getElementById('icon-demo')

// Keyed by EXTENSION (leading dot optional, case-insensitive) OR CATEGORY.
// Extension keys win over category keys, which win over the built-in icon.
el.fileIcons = {
  psd:     '<svg โ€ฆ>โ€ฆ</svg>',            // extension โ†’ inline SVG
  fig:     '<img src="โ€ฆ" alt="">',            // extension โ†’ <img>
  sketch:  '๐Ÿ’Ž',                              // extension โ†’ emoji
  archive: '๐Ÿ“ฆ',                              // whole CATEGORY โ†’ emoji
}

// Consulted BEFORE the map. Return HTML to use it, or null to fall through.
el.fileIconCallback = (file) =>
  /^invoice/i.test(file.name) ? '๐Ÿงพ' : null