Skip to content

File Upload ​

Lets users choose one or more files for upload. It looks like a regular input with a "Browse" button on the side. Clicking the field or the button opens the native file picker, and the chosen files are shown as tags inside the field.

Usage ​

Use the File Upload when a form needs the user to attach files, such as a document, an avatar image, or supporting evidence. Constrain the selection to what you actually accept: set accept to limit file types and add multiple only when several files are genuinely allowed. Pair it with a clear label describing what to upload and any size or format requirements, and prefer it over a bare native file input when you want the selection to stay visible and on-brand. For a single short text value, use a plain Input instead.

Behavior ​

The tags always show what the input holds. Emptying it puts the placeholder back, whether that happens through the picker, a form reset, or setting the input's value to '' in code, which is how the same file is offered for picking a second time.

API Reference ​

Component attribute(s) ​

x-h-file-upload

x-h-file-upload is placed on the same element as x-h-input-group. The group must contain:

  • an <input type="file"> (the real form control, which is hidden but stays focusable and submits with the form),
  • an x-h-tag-group element (the selected files are rendered into it),
  • a Browse button (any clickable element inside the group opens the picker).

Attributes ​

x-h-file-upload ​

AttributeValuesRequiredDescription
data-placeholderstringfalseText shown when no file is selected (default: "No file chosen").

The <input type="file"> keeps its native attributes - set multiple, accept, required, name and disabled directly on it. The selected files are shown as display-only tags. Picking again replaces the selection, mirroring the native control (there is no per-file remove button).

Model ​

There is no x-model. The native <input type="file"> is the source of truth: listen to its change event and read its .files, exactly as you would with a plain file input.

Data Slots ​

SlotElement
input-group-controlThe file input inside x-h-file-upload
file-input-placeholderPlaceholder created by x-h-file-upload

Examples ​

Single file ​

html
<div x-h-input-group x-h-file-upload>
  <input type="file" />
  <div x-h-input-group-addon data-align="inline-start">
    <div x-h-tag-group></div>
  </div>
  <div x-h-input-group-addon data-align="inline-end">
    <button type="button" x-h-button.addon>Browse</button>
  </div>
</div>

Multiple files with a custom placeholder ​

html
<div x-h-input-group x-h-file-upload data-placeholder="Select images...">
  <input type="file" multiple accept="image/*" />
  <div x-h-input-group-addon data-align="inline-start">
    <div x-h-tag-group></div>
  </div>
  <div x-h-input-group-addon data-align="inline-end">
    <button type="button" x-h-button.addon>Browse</button>
  </div>
</div>

Reacting to selection ​

Listen to the native change event on the file input to react to the chosen files.

html
<div x-h-input-group x-h-file-upload x-data>
  <input type="file" multiple @change="console.log([...$event.target.files].map((f) => f.name))" />
  <div x-h-input-group-addon data-align="inline-start">
    <div x-h-tag-group></div>
  </div>
  <div x-h-input-group-addon data-align="inline-end">
    <button type="button" x-h-button.addon>Browse</button>
  </div>
</div>

Invalid ​

Reacts to the native invalid state or to the aria-invalid attribute.

html
<div x-h-input-group x-h-file-upload>
  <input type="file" aria-invalid="true" />
  <div x-h-input-group-addon data-align="inline-start">
    <div x-h-tag-group></div>
  </div>
  <div x-h-input-group-addon data-align="inline-end">
    <button type="button" x-h-button.addon>Browse</button>
  </div>
</div>

Disabled ​

Set the native disabled attribute on the file input to disable the whole control.

html
<div x-h-input-group x-h-file-upload>
  <input type="file" disabled />
  <div x-h-input-group-addon data-align="inline-start">
    <div x-h-tag-group></div>
  </div>
  <div x-h-input-group-addon data-align="inline-end">
    <button type="button" x-h-button.addon>Browse</button>
  </div>
</div>