Skip to content

FileField

<caos-file-field>

attach files to a record, with each one’s own progress and its own failure

May be placed on a record page, a page inside an app and a step of a guided flow.

Name Attribute Description Type Default Required
label label, markup only The field name above the drop target. It also names the target for a screen reader, so leaving it off leaves an unnamed control. text none declared Optional
accept accept, markup only What may be attached, in the same grammar a native file input takes — .pdf, image/png, image/*, comma-separated. It is enforced on drop and paste as well as on the picker, and a file that does not match is named as refused rather than quietly ignored. Absent means anything is accepted. text none declared Optional
multiple multiple, markup only More than one file may be attached. Without it the field holds one: a second pick REPLACES the first rather than being ignored, because a pick that appears to do nothing reads as a broken control. boolean false Optional
maxBytes max-bytes, markup only The largest file this will take, in bytes. A file over it is refused by name with both sizes in the message, so the person can see how far over it is. Absent or 0 means no limit here. number none declared Optional
required required, markup only Something has to be attached. Draws the asterisk after the label and marks the drop target required. boolean false Optional
disabled disabled, markup only The field may not be used. Refuses the picker, drop and paste, and takes the drop target out of the tab order. This is NOT how progress is shown — a file in flight leaves the field usable. boolean false Optional
invalid invalid, markup only The answer is wrong — usually a required field with nothing attached. Turns the drop target red and marks it invalid. boolean false Optional
message message, markup only The line under the field. Setting it writes the line; the field also writes it itself to name a refused file, so a page that sets it on every render will overwrite what the field is trying to say. text none declared Optional
Name When it fires What it carries
caos-file-change Whenever what is attached changes — a file finishes, fails, is removed, or a whole batch is refused. Not while a file is merely in flight: an upload that has not finished is not a value yet. { value, busy } — the attached files, and whether anything is still in flight.

Nothing goes inside this piece.

Design token What it controls
--caos-color-border The dashed outline of the drop target, and the outline of a settled row.
--caos-color-accent The outline and wash while a file is dragged over it, and the moving part of the spinner.
--caos-color-surface-2 The fill of the drop target.
--caos-color-surface The fill of an attached row.
--caos-color-danger The required asterisk, the outline of a failed row, and the refusal message.
--caos-color-danger-bg The fill of a failed row.
--caos-color-danger-text The reason under the name on a failed row.
--caos-radius The corners of the drop target.
--caos-radius-sm The corners of a row, its thumbnail and its buttons.
--caos-font The typeface throughout.
--caos-text-body The size of the label and the drop target text.
--caos-text-row The size of an attached file name.
--caos-text-label The size of the size/status line and the message.
Name Description Arguments
clear() Forget everything attached — for a form that has just been submitted and is being reused. An upload still in flight is abandoned rather than allowed to reappear when it lands. takes none
focus() Move keyboard focus to the drop target, so Enter or Space opens the picker. options: FocusOptions
Name Type What it tells you
value a list of { name, url, size, type } The attached files. Only the ones that finished — a file in flight and a file that failed are both absent, so reading this never reports a file the far end does not have.
busy boolean Whether any file is still in flight. Read it to decide whether a form may be submitted yet; the field does not disable itself to say so.
Part Which piece of it When it is there
::part(control) the label, drop target, list and message together Always
::part(dropzone) the drop target that also opens the picker Always
::part(label) the field name above the drop target Always
::part(list) the list of attached files Always
::part(item) one attached file — carries data-state of uploading, done or failed Always
::part(spinner) the busy indicator on a row that is uploading Always
::part(message) the line under the field Always

Somebody needs to hand the system a file — a screenshot with a report, a document on a record. If what you want is a picture already stored somewhere, this is not it: this takes a file IN.

  • FieldRenderer — The type is not known until the record is read — hand it the field metadata and it draws this one for a File field.
  • ProgressBar — You are reporting how far along a long task is, rather than taking a file.
  • ValidationSummary — Several fields are wrong at once and the page needs to say so in one place.

The drop target is a real focusable control with role="button", reached by Tab and opened with Enter or Space, and it is named from label so it is not announced as an unlabelled button. aria-required and aria-invalid follow their attributes, and a disabled field reports aria-disabled and leaves the tab order rather than staying focusable and inert. A file in flight sets aria-busy on its own row, so what is announced as busy is the file rather than the whole field. Refusals are written into a role="status" live region, so a file rejected on drop is announced rather than only drawn — the case where a person never sees the message because they were not looking at that line. What is left to the author is the label, and a max-bytes and accept that match what the far end will actually take.

The resting state: a drop target that is also the picker, and says all three ways a file can arrive.

{
"id": "example",
"section": "Waiting for a file",
"columns": 1,
"items": [
{
"id": "file_field_1",
"type": "component",
"key": "file_field",
"inputs": {
"label": "Screenshots",
"accept": "image/*",
"multiple": true
}
}
]
}
<caos-file-field label="Screenshots" accept="image/*" multiple></caos-file-field>

A single-file field that takes one PDF up to 5MB. A file over the limit is named in the message with both sizes, rather than being dropped in silence.

{
"id": "example",
"section": "With a limit",
"columns": 1,
"items": [
{
"id": "file_field_1",
"type": "component",
"key": "file_field",
"inputs": {
"label": "Signed order",
"accept": ".pdf",
"max_bytes": 5242880
}
}
]
}
<caos-file-field label="Signed order" accept=".pdf" max-bytes="5242880"></caos-file-field>

A required field with nothing attached, and the reason underneath.

{
"id": "example",
"section": "Required and refused",
"columns": 1,
"items": [
{
"id": "file_field_1",
"type": "component",
"key": "file_field",
"inputs": {
"label": "Proof of delivery",
"required": true,
"invalid": true,
"message": "A photo of the delivery is required before this can be closed."
}
}
]
}
<caos-file-field label="Proof of delivery" required invalid message="A photo of the delivery is required before this can be closed."></caos-file-field>