Menu


File upload


A place to choose files or drop them, with what is accepted said first, and each chosen file in a row below with its progress and result.

Customize the preview


Choices
Flags
View

Choose files or drag them here

PDF or Word, up to 10 MB each, 5 files at most.

Options


File upload options
ChoiceValuesDefaultWhat it does
typesmbk-file-upload--type-<value>drop-zonebutton-onlydrop-zone
A drop zone is a dashed area with a title, what is accepted and a button, that also takes files dropped on it; button only is the button over what is accepted, for narrow places. Figma calls them "Drop zone" and "Button only".
errorsmbk-file-upload--errorOn or offOff
Something is wrong, such as no file chosen when one is required. The zone takes the danger edge; say what is wrong in the message.
draggingsmbk-file-upload--draggingOn or offOff
Files are held over the zone: the brand edge, the sunken surface and "Drop to add", at the same height. The wrapper sets it while dragging; Figma calls it "Drag-over".

Partssmbk-file-upload__zonesmbk-file-upload__bodysmbk-file-upload__iconsmbk-file-upload__textsmbk-file-upload__titlesmbk-file-upload__constraintssmbk-file-upload__buttonsmbk-file-upload__inputsmbk-file-upload__messagesmbk-file-upload__dropsmbk-file-upload__filessmbk-file-upload__filesmbk-file-upload__announcement

Examples


Usage

Press Browse files, or drop files on the zone. Each file gets a row that shows its progress, then whether it went through; a file of the wrong type or too large is not added, and says why.

Choose files or drag them here

PDF or Word, up to 10 MB each, 5 files at most.

  • quarterly-report.pdf

    1.1 of 2.4 MBUploading…

  • board-minutes.docx

    830 kBUploaded

  • signed-contract.pdf

    Upload failed

Button only

The button over what is accepted, for narrow places such as a phone, where nothing is dragged.

PDF or Word, up to 10 MB each, 5 files at most.

  • quarterly-report.pdf

    1.1 of 2.4 MBUploading…

  • signed-contract.pdf

    Upload failed

  • site-photo.heic

    Not added: it must be a PDF.

  • board-minutes.docx

    830 kBUploaded

One file

For one file the words say so, and a new choice takes the old one’s place.

Choose a file or drag it here

PDF or Word, up to 10 MB.

  • signed-contract.pdf

    4.2 MB

Drag over

While files are held over it, the zone turns its edge brand and its surface sunken and says “Drop to add”, at the same height.

Choose files or drag them here

PDF or Word, up to 10 MB each, 5 files at most.

Error

The danger edge, and the message in the zone under the button, saying what to do.

Choose files or drag them here

PDF or Word, up to 10 MB each, 5 files at most.

Disabled

Dimmed and out of reach, the button with it.

Choose files or drag them here

PDF or Word, up to 10 MB each, 5 files at most.

Guidelines


Purpose

Lets the reader add files to a form or a library: by pressing a button that opens the browser's own file picker, or by dropping files on a zone. It says what is accepted before anyone chooses, and shows each chosen file in a row under it with its progress, then whether it went through. A plain file input shows none of that.

When to use

  • A form that takes documents, such as a claim with its receipts or an application with its papers.
  • A library or a workspace where files are added and uploaded one by one.
  • A single file, such as a picture for a profile or a signed contract: set it to take one.

When not to use

  • A choice among files already on the server, such as a picture from a library: that is a picker of its own.
  • Text that could be typed or pasted into a text area.
  • A narrow place such as a phone, for the drop zone: nothing is dragged there. Use the button-only form.

Usage rules

Do

  • Say what is accepted in the constraints: the types, the largest size, how many.
  • Put it in a field, so the input is named.
  • Say why a file was not added, in its row: "Not added: it must be a PDF."
  • Use the button-only form in narrow places.

Don't

  • Make dropping the only way to add a file.
  • Trust the file's extension or type: the server checks what it takes.
  • Read out every percent of an upload.

Choose files or drag them here

PDF or Word, up to 10 MB each, 5 files at most.

Do

Say what is accepted before anyone chooses: the types, the largest size, how many.

Choose files or drag them here

Don't

Without the line the reader learns the rules only when a file is refused.

A photo, up to 5 MB.

Do

In a narrow place, such as a phone, use the button-only form: nothing is dragged there.

Choose a file or drag it here

A photo, up to 5 MB.

Don't

A drop zone on a phone takes room for a way of adding files no one can use there.

Anatomy

  • The block, a column: the zone, then the list of files, then the announcement part.
  • The zone part. In a drop zone it is a dashed area at the medium container corners; in the button-only form it draws nothing of its own. It holds the body part and, in a drop zone, the drop part, in one place, so the zone is as tall as its body whichever shows.
  • The body part. In a drop zone: the icon part (an upload arrow), the text part with the title part over the constraints part, the button part, and in error the message part. In the button-only form: the button, the constraints and the message, stacked from the start edge.
  • The button part, a label round the input carrying the button's secondary classes at the medium size, so its looks are the button's.
  • The input part, the native file input inside the button: in the page and the tab order, unseen.
  • The constraints part: what is accepted, said before anyone chooses: the types, the largest size, how many.
  • The message part, a field message of the error type: what is wrong and what to do.
  • The drop part, shown in the body's place while files are held over the zone: the icon, "Drop to add" and the constraints, centred. It is hidden from assistive technology.
  • The files part, a list of the chosen files; each file part holds one file row.
  • The announcement part, a status region that is in the page but not drawn.
  • The type is a modifier: --type-drop-zone, --type-button-only. The --error and --dragging flags are modifiers too.
  • The label is not the upload's: put it in a field.

Behavior

The button opens the browser's own file picker, with or without script. Pressing it, or Enter or Space when the input has focus, opens the picker. A drop zone also takes files dropped anywhere on it. While files are held over it, its edge turns the brand color at the emphasis width, its surface turns sunken and its words turn to "Drop to add", and it keeps its height. A file dropped on a disabled upload is ignored, and is not opened in the page's place. The button-only form takes no drop.

The React wrapper uploads nothing. It hands what was chosen or dropped to onAdd, and draws a file row for each file in files, which the page keeps and updates as each upload goes. It passes each row's buttons on as onRemove, onCancel, onRetry and onDelete, with the file's id. With accept, a file of another type is not added: it is passed to onAdd in the refused list with the reason type, so the page can show it as not added and say why. Without multiple, the upload takes one file, and a second one dropped at the same time is refused with the reason count. The input is emptied after each choice, so the same file can be chosen again after it is removed. It is required only while no file is chosen, so a form with a required upload is not sent empty.

In error the zone draws the danger edge at the emphasis width; errorMessage shows the message in the zone, under the button, or under the constraints in the button-only form. Give the message to the upload, not to the field around it: the field would draw it a second time under the zone. The upload's error turns on the error look without a message. Disabled, the icon and the words take the disabled colors and the button its own disabled look. Read-only (readOnly, which a field passes on), the upload shows its files and takes no more: a browser has no read-only file input, so the button and the zone are drawn and behave as disabled, and the rows show without their buttons. A ref given to the React wrapper reaches the native input.

Accessibility


What it does for you

  • The input is named by the field's label, then the button's words ("Attachments Browse files"), so a reader using voice control can say either. The constraints and the error message are its description, read before anyone chooses.
  • Tab reaches the input, which the focus ring round the zone shows (round the button alone in the button-only form), then each row's buttons. Enter or Space opens the picker.
  • Dropping is an extra: the button always works.
  • Each row's buttons are named with the file: "Remove quarterly-report.pdf", "Cancel upload of quarterly-report.pdf", "Try again to upload quarterly-report.pdf", "Delete quarterly-report.pdf".
  • A file's change of status, a file added and a file removed are read out once each, with the file's name, in one polite status region: "quarterly-report.pdf: Uploaded". The progress in between is not read out.
  • When the button that had focus goes, as when a file is removed, focus moves to the next row's first button, else to the last row's, else to the input.
  • Disabled or read-only, the zone carries aria-disabled beside the input's disabled, so its faint title and line are read as part of a control out of reach.

What you must do

  • Put the upload in a field, so the input has a name, or give it an aria-label.
  • Say what is accepted in the constraints. accept and the constraints are hints for the reader; the server decides what it takes.
  • Change the words for another language: title, dropTitle, buttonLabel, addedLabel, removedLabel, the row's button names and sizeLabel, and locale for the sizes.
  • In markup written by hand, add aria-disabled and disabled together when it is disabled or read-only, and leave out the rows' buttons when read-only.

How to test

  • With the keyboard only, Tab to the upload: the focus ring shows round the zone (or the button). Press Enter, then Space: each opens the picker.
  • Add a file, then Tab to its row's buttons and remove it: focus moves to the next row's first button, else the last row's, else the input.
  • With a screen reader, the input is read by the field's label and the button's words, with the constraints; each row's buttons are read with the file's name.
  • Add, finish and remove a file: each change is read out once, and the progress in between is not.
  • With text at 200% and the page 320 pixels wide, use the button-only form: the button and the rows stay whole.
  • In a forced-color mode, the zone's edge, the focus ring and the buttons are still visible.

Keyboard

File upload keys
KeyWhat it does
Tab
Moves focus to the input, then to each row's buttons.
EnterorSpace
On the input, opens the picker. On a row's button, presses it.

Source


The files the file upload is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.

Stylesheet
file-upload.css
/* smbk-file-upload — File upload. Figma "File upload" 7110:345 (Type: Drop zone, Button only × State: Default,
 * Drag-over, Focus, Disabled, Error).
 * A native file input, unseen but in the tab order, inside a label drawn as the secondary button, so the browser's
 * own picker always opens: from a press on the button, or Enter or Space on the focused input. The drop zone is a
 * dashed area round an upload icon, a title over the line that says what is accepted, the button and, in error,
 * the field message; dropping files on it is an extra. Button only is the button over the same line, for narrow
 * places. Focus is the input's, drawn as the focus ring round the zone (round the button alone in the button-only
 * form); disabled is the input's attribute; error and dragging are flags. While files are held over it the zone
 * keeps its height and swaps its body for the drop layer: the upload icon and "Drop to add" over the same line,
 * centred, on the sunken surface inside the brand edge. The chosen files are file rows in a list under it.
 * The edge is drawn inside, so the paddings give its width back, at the hairline and at the emphasis width. */

.smbk-file-upload {
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-md);
  min-width: 0;
}

/* The block frames the words it hides for screen readers, so they stay inside it wherever it is placed: without a
   frame they hang from the page itself and stretch it, past the end of an area that scrolls. No weight, so a parent
   that places the block keeps its say. */
:where(.smbk-file-upload) {
  position: relative;
}

/* ---- the drop zone ---- */

.smbk-file-upload__zone {
  box-sizing: border-box;
  display: grid;
  min-width: 0;
  padding: calc(var(--smbk-space-inset-2xl) - var(--smbk-border-width-hairline));
  border: var(--smbk-border-width-hairline) dashed var(--smbk-color-border-strong);
  border-radius: var(--smbk-radius-container-md);
  background: var(--smbk-color-surface-default);
  text-align: center;
}

/* The body and the drop layer share one cell, so the zone is as tall as its body whichever shows. */
.smbk-file-upload__body,
.smbk-file-upload__drop {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--smbk-space-stack-lg);
  min-width: 0;
}

.smbk-file-upload__drop {
  justify-content: center;
  visibility: hidden;
}

.smbk-file-upload__icon {
  font-size: var(--smbk-size-icon-lg);
  color: var(--smbk-color-icon-subtle);
}

.smbk-file-upload__text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--smbk-space-stack-xs);
  min-width: 0;
}

.smbk-file-upload__title {
  margin: 0;
  color: var(--smbk-color-text-default);
  font-family: var(--smbk-typography-body-md-medium-font-family);
  font-size: var(--smbk-typography-body-md-medium-font-size);
  font-weight: var(--smbk-typography-body-md-medium-font-weight);
  line-height: var(--smbk-typography-body-md-medium-line-height);
  letter-spacing: var(--smbk-typography-body-md-medium-letter-spacing);
}

/* What is accepted: the types, the largest size, how many. */
.smbk-file-upload__constraints {
  margin: 0;
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-xs-font-family);
  font-size: var(--smbk-typography-body-xs-font-size);
  font-weight: var(--smbk-typography-body-xs-font-weight);
  line-height: var(--smbk-typography-body-xs-line-height);
  letter-spacing: var(--smbk-typography-body-xs-letter-spacing);
}

/* The button is a label round the input, carrying the button's classes, so its looks are the button's. */
.smbk-file-upload__button {
  position: relative;
  max-width: 100%;
}

/* The input stays in the page and the tab order, unseen; the button and the zone draw its focus. */
/* .smbk-file-upload__input: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css. */
.smbk-file-upload__input {
  clip-path: inset(50%);
}

/* The field message across the zone's width, its words from the start edge, as Figma draws it. */
.smbk-file-upload__message {
  align-self: stretch;
  text-align: start;
}

/* Focus: the ring goes round the zone, not the button inside it. */
.smbk-file-upload--type-drop-zone .smbk-file-upload__zone:has(.smbk-file-upload__input:focus-visible) {
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: var(--smbk-focus-ring-offset);
}

.smbk-file-upload--type-drop-zone .smbk-file-upload__button:has(> .smbk-file-upload__input:focus-visible) {
  outline: none;
}

/* Error: the danger edge at the emphasis width; its file input marked aria-invalid="true" draws the same. */
.smbk-file-upload--error .smbk-file-upload__zone,
.smbk-file-upload:where(:has([aria-invalid="true"])) .smbk-file-upload__zone {
  padding: calc(var(--smbk-space-inset-2xl) - var(--smbk-border-width-emphasis));
  border-width: var(--smbk-border-width-emphasis);
  border-color: var(--smbk-color-feedback-danger-border);
}

/* Dragging: the brand edge at the emphasis width and the sunken surface, the drop layer in the body's place.
   The button-only form takes no drop, so it has no dragging look. */
.smbk-file-upload--dragging:not(.smbk-file-upload--type-button-only) .smbk-file-upload__zone {
  padding: calc(var(--smbk-space-inset-2xl) - var(--smbk-border-width-emphasis));
  border-width: var(--smbk-border-width-emphasis);
  border-color: var(--smbk-color-border-brand);
  background: var(--smbk-color-surface-sunken);
}

.smbk-file-upload--dragging:not(.smbk-file-upload--type-button-only) .smbk-file-upload__body {
  visibility: hidden;
}

.smbk-file-upload--dragging:not(.smbk-file-upload--type-button-only) .smbk-file-upload__drop {
  visibility: visible;
}

/* Disabled: the icon and the words take the disabled colors; the button takes its own disabled look. */
.smbk-file-upload:has(.smbk-file-upload__input:disabled) .smbk-file-upload__icon {
  color: var(--smbk-color-icon-disabled);
}

.smbk-file-upload:has(.smbk-file-upload__input:disabled) :is(.smbk-file-upload__title, .smbk-file-upload__constraints) {
  color: var(--smbk-color-text-disabled);
}

/* ---- button only: the button over the line, stacked from the start edge, with no zone drawn ---- */

.smbk-file-upload--type-button-only .smbk-file-upload__zone {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  text-align: start;
}

.smbk-file-upload--type-button-only .smbk-file-upload__body {
  align-items: flex-start;
  gap: var(--smbk-space-inline-md);
}

/* ---- the chosen files ---- */

.smbk-file-upload__files {
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-md);
  margin: 0;
  padding: 0;
  list-style: none;
}

.smbk-file-upload__file {
  min-width: 0;
}

/* What changed, for a screen reader: in the page, not drawn. */
/* .smbk-file-upload__announcement: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css. */
.smbk-file-upload__announcement {
  clip-path: inset(50%);
}

/* ---- high contrast (forced colors) ---- */
/* The dragging zone's sunken surface is dropped in Windows high-contrast mode, so its edge says it: Highlight. */

@media (forced-colors: active) {
  .smbk-file-upload--dragging:not(.smbk-file-upload--type-button-only) .smbk-file-upload__zone {
    border-color: Highlight;
  }
}
Tokens it reads
File upload tokens
Token and CSSValuePreviewFoundation
space.stack.md--smbk-space-stack-mdcomfortable: 12pxcompact: 8pxchanges with densitySpacing
space.inset.2xl--smbk-space-inset-2xlcomfortable: 24pxcompact: 16pxchanges with densitySpacing
border-width.hairline--smbk-border-width-hairline1pxBorder width
color.border.strong--smbk-color-border-stronglight: #9ba5b1dark: #373d43changes with themeColor
radius.container.md--smbk-radius-container-md12pxRadius
color.surface.default--smbk-color-surface-defaultlight: #f3f5f8dark: #181a1cchanges with themeColor
space.stack.lg--smbk-space-stack-lgcomfortable: 16pxcompact: 12pxchanges with densitySpacing
size.icon.lg--smbk-size-icon-lg24pxSize
color.icon.subtle--smbk-color-icon-subtlelight: #4e555edark: #c4cdd8changes with themeColor
space.stack.xs--smbk-space-stack-xs4pxSpacing
color.text.default--smbk-color-text-defaultlight: #232527dark: #e7ebf0changes with themeColor
typography.body-md-medium--smbk-typography-body-md-medium-font-family--smbk-typography-body-md-medium-font-size--smbk-typography-body-md-medium-font-weight--smbk-typography-body-md-medium-line-height--smbk-typography-body-md-medium-letter-spacing--smbk-typography-body-md-medium-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 500lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
color.text.subtle--smbk-color-text-subtlelight: #4e555edark: #9ba5b1changes with themeColor
typography.body-xs--smbk-typography-body-xs-font-family--smbk-typography-body-xs-font-size--smbk-typography-body-xs-font-weight--smbk-typography-body-xs-line-height--smbk-typography-body-xs-letter-spacing--smbk-typography-body-xs-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
focus.ring.width--smbk-focus-ring-width2pxFocus
color.border.focus--smbk-color-border-focuslight: #cc0d0ddark: #f34336changes with themeColor
focus.ring.offset--smbk-focus-ring-offset2pxFocus
border-width.emphasis--smbk-border-width-emphasis2pxBorder width
color.feedback.danger.border--smbk-color-feedback-danger-border#f34336Color
color.border.brand--smbk-color-border-brand#cc0d0dColor
color.surface.sunken--smbk-color-surface-sunkenlight: #373d430ddark: #00000066changes with themeColor
color.icon.disabled--smbk-color-icon-disabledlight: #9ba5b1dark: #606a75changes with themeColor
color.text.disabled--smbk-color-text-disabledlight: #7c8692dark: #606a75changes with themeColor
space.inline.md--smbk-space-inline-mdcomfortable: 8pxcompact: 6pxchanges with densitySpacing
Manifest
file-upload.manifest.json
{
  "name": "file-upload",
  "block": "smbk-file-upload",
  "summary": "A place to choose files or drop them, with what is accepted said first, and each chosen file in a row below with its progress and result.",
  "adapt": "css",
  "axes": {
    "type": {
      "values": [
        "drop-zone",
        "button-only"
      ],
      "default": "drop-zone",
      "description": "A drop zone is a dashed area with a title, what is accepted and a button, that also takes files dropped on it; button only is the button over what is accepted, for narrow places. Figma calls them \"Drop zone\" and \"Button only\"."
    }
  },
  "flags": {
    "error": {
      "description": "Something is wrong, such as no file chosen when one is required. The zone takes the danger edge; say what is wrong in the message."
    },
    "dragging": {
      "description": "Files are held over the zone: the brand edge, the sunken surface and \"Drop to add\", at the same height. The wrapper sets it while dragging; Figma calls it \"Drag-over\"."
    }
  },
  "parts": [
    "zone",
    "body",
    "icon",
    "text",
    "title",
    "constraints",
    "button",
    "input",
    "message",
    "drop",
    "files",
    "file",
    "announcement"
  ],
  "figmaNodeId": "7110:345"
}