Connection lost. Reconnecting… attempt 1 of 8
Paused. Your work is held on the server.
Could not reconnect.
This session has expired on the server.
DR.Simple_UI
Showing main. This can be ahead of the version your app has installed — check what a class says it needs before copying it. Releases (opens in a new tab)

File inputs

tier 2 — classes

A picker that can be styled, and a dropzone that takes a real drop.

File input

<input type="file"> cannot be restyled: its button is a shadow-DOM part with only ::file-selector-button exposed, and its “No file chosen” text is not addressable at all. .form-file hides the input behind a label styled as a button, which keeps the real control — a <label> for a file input opens the picker on click and on Enter, with no JavaScript and no ARIA.

The input is clipped rather than display: none, so it stays focusable and in the accessibility tree. The focus ring is drawn on the label, because the focused element is the one the reader cannot see.

::file-selector-button is styled too, for anyone using the input bare.

Chromium lays a file input out around its own button, so the block padding is the engine's rather than ours — the box height still comes from the control tier.
<div class="dr-col dr-gap-2" style="max-width:460px">
    <div class="form-field">
        <label class="form-label" for="ff-styled">Attachment</label>
        <label class="form-file">
            <input type="file" id="ff-styled" />
            <span class="btn"><i class="ri-attachment-2"></i> Choose a file</span>
            <span class="form-file-name">No file chosen</span>
        </label>
    </div>
    <div class="form-field">
        <label class="form-label" for="ff-bare">The same input, bare</label>
        <input class="form-input" id="ff-bare" type="file" />
        <span class="form-hint">Chromium lays a file input out around its own button, so the block
        padding is the engine's rather than ours — the box height still comes from the control
        tier.</span>
    </div>
</div>

Dropzone and file list

Live — drag a file onto it. A drop target that is also a click target: drag and drop alone is unusable by keyboard and awkward on touch, so the zone wraps a real file input and the drag handling is an enhancement on top.

data-dropzone hands it to drSimpleUi.dropzone, which maintains .dropzone--over and puts a dropped file into the zone's own input as a change event — so an existing handler, Blazor's InputFile included, sees a dropped file exactly as it sees a chosen one. Leave the attribute off to own the drag handling yourself.

Two things it does that are easy to get wrong alone: dragleave fires when the pointer moves onto a child, so a naive enter/leave pair flickers and then sticks as soon as the zone contains an icon and a label. And dragover must have its default prevented, or the browser refuses the drop and navigates to the file.

The file list is a real <ul>, so the count is announced. Sizes are monospace so a column of them lines up. .file-item--error carries the icon as well as the colour.

  • order-export-2026-07-31.pdf 1.2 MB
  • screen recording.mov — not an accepted type 48 MB
<div style="max-width:460px">
    <label class="dropzone" data-dropzone>
        <input type="file" multiple class="visually-hidden" />
        <i class="ri-upload-cloud-2-line"></i>
        <span>Drop files here, or click to choose</span>
        <span class="form-hint">PDF, PNG or EML — up to 10 MB each</span>
    </label>
    <ul class="file-list">
        <li class="file-item">
            <i class="ri-file-pdf-2-line"></i>
            <span class="file-item-name">order-export-2026-07-31.pdf</span>
            <span class="file-item-size">1.2 MB</span>
            <button class="chip-dismiss" type="button" aria-label="Remove order-export-2026-07-31.pdf">
                <i class="ri-close-line"></i>
            </button>
        </li>
        <li class="file-item file-item--error">
            <i class="ri-error-warning-line"></i>
            <span class="file-item-name">screen recording.mov — not an accepted type</span>
            <span class="file-item-size">48 MB</span>
            <button class="chip-dismiss" type="button" aria-label="Remove screen recording.mov">
                <i class="ri-close-line"></i>
            </button>
        </li>
    </ul>
</div>

The three states

Idle, dragged-over, and rejected. .dropzone--over is set by the script and has no CSS pseudo-class equivalent, so it is the one state an app cannot express in markup alone.

Rejection is aria-invalid on the zone plus a .form-error naming the file and the limit. Set the attribute rather than a class: it is what a screen reader announces, so the two cannot disagree. “Invalid file” is not a message — say which file and why.

Drop files here, or click to choose Something is being dragged over it
Drop files here, or click to choose Rejected

screen recording.mov is 48 MB. The limit is 10 MB.

<div class="dr-col dr-gap-2" style="max-width:460px">
    <label class="dropzone" data-dropzone>
        <input type="file" multiple class="visually-hidden" />
        <i class="ri-upload-cloud-2-line"></i>
        <span>Drop files here, or click to choose</span>
        <span class="form-hint">Idle</span>
    </label>
    <div class="dropzone dropzone--over">
        <i class="ri-upload-cloud-2-line"></i>
        <span>Drop files here, or click to choose</span>
        <span class="form-hint">Something is being dragged over it</span>
    </div>
    <div>
        <div class="dropzone" aria-invalid="true">
            <i class="ri-error-warning-line"></i>
            <span>Drop files here, or click to choose</span>
            <span class="form-hint">Rejected</span>
        </div>
        <p class="form-error" id="dz-error">
            <i class="ri-error-warning-line"></i> screen recording.mov is 48 MB. The limit is 10 MB.
        </p>
    </div>
</div>

Inside a form

The whole field in place: a <fieldset class="form-section"> so the legend is announced with every control inside it, the zone, the list of what is attached, and the .form-actions row the form ends with.

.form-label--required is decoration. Put required on the input, which is what is announced and enforced.

Proof of delivery

Attached to ORD-4209 and visible to the customer.

  • pod-ord-4209.pdf 640 KB
<fieldset class="form-section" style="max-width:520px">
    <legend class="form-section-title">Proof of delivery</legend>
    <p class="form-section-hint">Attached to ORD-4209 and visible to the customer.</p>
    <div class="form-section-body">
        <div class="form-field">
            <label class="form-label form-label--required" for="pod-file">Signed note</label>
            <label class="dropzone" data-dropzone>
                <input type="file" id="pod-file" required />
                <i class="ri-upload-cloud-2-line"></i>
                <span>Drop the scan here, or click to choose</span>
                <span class="form-hint">PDF or PNG, up to 10 MB</span>
            </label>
        </div>
        <ul class="file-list">
            <li class="file-item">
                <i class="ri-file-pdf-2-line"></i>
                <span class="file-item-name">pod-ord-4209.pdf</span>
                <span class="file-item-size">640 KB</span>
                <button class="chip-dismiss" type="button" aria-label="Remove pod-ord-4209.pdf">
                    <i class="ri-close-line"></i>
                </button>
            </li>
        </ul>
        <div class="form-field">
            <label class="form-label" for="pod-note">Note</label>
            <input class="form-input" id="pod-note" type="text" placeholder="Left with the concierge" />
        </div>
    </div>
    <div class="form-actions">
        <button class="btn" type="button">Cancel</button>
        <button class="btn btn-go" type="button"><i class="ri-check-line"></i> Attach and close</button>
    </div>
</fieldset>