Menu


File row


One chosen file: its name, size and state, its progress while it uploads, and what can be done with it.

Customize the preview


Choices
View

quarterly-report.pdf

2.4 MB

Options


File row options
ChoiceValuesDefaultWhat it does
statussmbk-file-row--status-<value>selecteduploadingcompletefailedrejectedselected
Selected is chosen and waiting; uploading shows the spinner and the progress bar; complete has gone through; failed did not, and can be tried again; rejected was not added, and says why.

Partssmbk-file-row__topsmbk-file-row__iconsmbk-file-row__textsmbk-file-row__namesmbk-file-row__stemsmbk-file-row__extensionsmbk-file-row__metasmbk-file-row__sizesmbk-file-row__separatorsmbk-file-row__statesmbk-file-row__state-iconsmbk-file-row__messagesmbk-file-row__actionssmbk-file-row__progress

Examples


Usage

A file going up: how much of it has gone, the turning ring and “Uploading…”, the bar under it, and a button that stops it.

quarterly-report.pdf

1.1 of 2.4 MBUploading…

Statuses

Chosen and waiting, uploading, uploaded, failed with Try again beside Remove, and refused with the reason.

quarterly-report.pdf

2.4 MB

quarterly-report.pdf

1.1 of 2.4 MBUploading…

quarterly-report.pdf

2.4 MBUploaded

quarterly-report.pdf

Upload failed

quarterly-report.xlsx

Not added: it must be a PDF.

Long name

A long name is cut short with an ellipsis; the extension stays whole.

quarterly-report-final-version-approved-by-the-board.pdf

2.4 MBUploaded

File types

The icon follows the file’s type: a document, a spreadsheet, slides, a picture, a sound, an archive.

board-minutes.docx

830 kB

budget-2027.xlsx

1.2 MB

launch-deck.pptx

18.6 MB

site-photo.jpg

3.1 MB

interview.mp3

42 MB

brand-assets.zip

120 MB

Narrow

In a narrow place the line keeps the size and the state together; a long reason wraps beside its own icon.

quarterly-report.pdf

1.1 of 2.4 MBUploading…

quarterly-report.pdf

Upload failed

quarterly-report.pdf

Not added: it is larger than 10 MB.

Unknown progress

Without its size or progress the bar shows its unknown state, and the line says only “Uploading…”.

interview.mp3

Uploading…

Guidelines


Purpose

One chosen file, shown as a card: its type, its name, its size and its state, its progress while it uploads, and the buttons for what can be done with it. A file upload draws one for each file chosen; a list of attachments can use it on its own.

When to use

  • Each file chosen in a file upload, which draws them for you.
  • A list of files already attached, such as the attachments of a message.

When not to use

  • A file to open or download in a list of documents: use a list, whose rows are links.
  • A folder of files to browse: use a table or a list.

Usage rules

Do

  • Say why a file was not added, in its message: "Not added: it must be a PDF."
  • Give only the handlers the page can carry out.

Don't

  • Show a file as uploaded before the server has taken it.
  • Read out every percent of an upload.

site-photo.heic

Not added: it must be a PDF.

Do

Say why a file was not added, so the reader can put it right.

site-photo.heic

Not added

Don't

A bare “Not added” leaves the reader guessing what to change.

Anatomy

  • The block, a card with a subtle edge: the top part, then, while the file uploads, the progress part.
  • The top part, a row: the icon part, the text part, then the actions part at the top end.
  • The icon part: the file's type, such as a document, a spreadsheet or a picture, picked from its extension or its media type, at the 2xl icon size.
  • The text part: the name part over the meta part.
  • The name part: the stem part, the name before its extension, then the extension part. A long name is cut short with an ellipsis in the stem, and the extension stays whole.
  • The meta part, one line: the size part, the separator part (a dot), then the state part. The size shows while the file is chosen, uploading or uploaded; while it uploads it says how much has gone ("1.1 of 2.4 MB").
  • The state part: the state-icon part (the spinner's subtle ring while uploading, a tick once uploaded, a crossed circle when it failed, a warning sign when it was not added) and the message part, the words. A failed or refused file shows only its icon and reason, which wraps beside its icon.
  • The actions part: the button's small ghost icon-only form, named with the file: Remove for a file chosen, failed or not added; Cancel while it uploads; Try again then Remove when it failed; Delete once uploaded.
  • The progress part: the progress bar's small size with no label, the row's full width, named with the file.
  • The status is a modifier: --status-selected, --status-uploading, --status-complete, --status-failed, --status-rejected.

Behavior

The row is markup and words only: it uploads nothing, and the page sets its status and its progress. Each button is drawn only when its handler is given, so a row offers only what the page can do. While the file uploads, the bar fills from loaded and size, or from progress; without either it shows its unknown state. The spinner's ring turns while the file uploads, and stands still under reduced motion. Sizes are written in English, or in the language set with locale, so a page prepared on a server shows the same sizes once its script runs.

Accessibility


What it does for you

  • The file's icon is decoration, hidden from assistive technology: the name says the same.
  • Each button is named with the file, such as "Remove quarterly-report.pdf".
  • The state is an icon and words, never color alone. A failed or refused file says why in words.
  • The progress bar is named with the file.
  • The name is written as text, never as markup, and follows its own direction, so a name in a right-to-left script reads the right way.
  • A row alone announces nothing. In a file upload, its changes of status are read out for it.

What you must do

  • Use removeLabel, cancelLabel, retryLabel and deleteLabel to change the buttons' words for another language.
  • Say why a file was refused, in its message.
  • On a page that uses rows on their own, announce their changes yourself, once each, not every percent.

How to test

  • With a screen reader, each button is read with the file's name, and the progress bar is read by the file.
  • Tab through the row: each button takes focus, and Enter or Space presses it.
  • With the colors removed, the icon and words still say whether the file is uploading, done, failed or refused.
  • With text at 200% and the page 320 pixels wide, a long name is cut short with an ellipsis, its extension stays whole, and the buttons stay in view.
  • In a forced-color mode, the icon and the buttons are still visible.

Keyboard

File row keys
KeyWhat it does
Tab
Moves focus to the next button in the row.
EnterorSpace
Presses the focused button: remove, cancel, try again or delete.

Source


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

Stylesheet
file-row.css
/* smbk-file-row — File row. Figma "File row" 7110:252 (Status: Selected, Uploading, Complete, Failed, Rejected).
 * One chosen file: a card that stacks a top row over, while it uploads, the progress bar. The top row holds the
 * file's icon at the 2xl icon size, then its name over a line with the size, a dot and the state, then its
 * actions, the button's small ghost icon-only form, at the top end. The name is two texts, so a long one is cut
 * short with an ellipsis and the extension stays whole. The state is an icon (or the spinner's ring) and words,
 * so it never rests on color alone; a failed or refused file shows only the reason, which wraps beside its icon.
 * The edge is drawn inside, so the paddings give its width back. */

.smbk-file-row {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-md);
  min-width: 0;
  padding: calc(var(--smbk-space-inset-lg) - var(--smbk-border-width-hairline));
  border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
  border-radius: var(--smbk-radius-container-sm);
  background: var(--smbk-color-surface-default);
  color: var(--smbk-color-text-default);
}

.smbk-file-row__top {
  display: flex;
  align-items: flex-start;
  gap: var(--smbk-space-inline-lg);
  min-width: 0;
}

/* The file's type, as an icon chosen from its extension: decoration, since the name says the same. */
.smbk-file-row__icon {
  flex: none;
  font-size: var(--smbk-size-icon-2xl);
  color: var(--smbk-color-icon-subtle);
}

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

.smbk-file-row__name {
  display: flex;
  min-width: 0;
  margin: 0;
  color: var(--smbk-color-text-default);
  font-family: var(--smbk-typography-body-sm-medium-font-family);
  font-size: var(--smbk-typography-body-sm-medium-font-size);
  font-weight: var(--smbk-typography-body-sm-medium-font-weight);
  line-height: var(--smbk-typography-body-sm-medium-line-height);
  letter-spacing: var(--smbk-typography-body-sm-medium-letter-spacing);
}

/* The name before its extension shrinks first, cut short with an ellipsis; the extension keeps its width. */
.smbk-file-row__stem {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.smbk-file-row__extension {
  flex: none;
  white-space: nowrap;
}

/* The line under the name: the size, a dot, then the state. It never wraps between them; only a long reason
   wraps, beside its own icon. */
.smbk-file-row__meta {
  display: flex;
  align-items: flex-start;
  gap: var(--smbk-space-inline-sm);
  min-width: 0;
  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);
}

.smbk-file-row__size,
.smbk-file-row__separator {
  flex: none;
  white-space: nowrap;
}

/* Figures of one width, so the size counting up while a file uploads ("1.1 of 2.4 MB") never moves the state after it. */
.smbk-file-row__size {
  font-variant-numeric: tabular-nums;
}

.smbk-file-row__state {
  display: flex;
  flex: 1;
  align-items: flex-start;
  gap: var(--smbk-space-inline-xs);
  min-width: 0;
}

/* The state's icon, and the spinner's ring while uploading, sit in a box one line tall, on the first line's middle. */
.smbk-file-row__state-icon {
  flex: none;
  height: calc(var(--smbk-typography-body-xs-font-size) * var(--smbk-typography-body-xs-line-height));
  font-size: var(--smbk-size-icon-sm);
}

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

.smbk-file-row--status-complete .smbk-file-row__message {
  color: var(--smbk-color-feedback-success-text-on-subtle);
}

.smbk-file-row--status-complete .smbk-file-row__state-icon {
  color: var(--smbk-color-feedback-success-icon);
}

.smbk-file-row--status-failed .smbk-file-row__message,
.smbk-file-row--status-rejected .smbk-file-row__message {
  color: var(--smbk-color-feedback-danger-text-on-subtle);
}

.smbk-file-row--status-failed .smbk-file-row__state-icon,
.smbk-file-row--status-rejected .smbk-file-row__state-icon {
  color: var(--smbk-color-feedback-danger-icon);
}

.smbk-file-row__actions {
  display: flex;
  flex: none;
  gap: var(--smbk-space-inline-xs);
}

/* While uploading: the progress bar's small size, with no label, the row's full width. */
.smbk-file-row__progress {
  width: 100%;
}

/* ---- touch: the actions stop their press areas halfway to each other ----
   Every button takes an invisible press area on a touch screen; the row's actions stand closer than that, so between
   two of them each area reaches only halfway into their gap. Nothing drawn moves. */

@media (pointer: coarse) {
  .smbk-file-row__actions > * + * .smbk-btn::after,
  .smbk-file-row__actions > .smbk-btn + .smbk-btn::after,
  .smbk-file-row__actions > * + .smbk-btn::after {
    inset-inline-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-xs) / -2);
  }

  .smbk-file-row__actions > *:has(+ *) .smbk-btn::after,
  .smbk-file-row__actions > .smbk-btn:has(+ *)::after {
    inset-inline-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-xs) / -2);
  }
}
Tokens it reads
File row tokens
Token and CSSValuePreviewFoundation
space.stack.md--smbk-space-stack-mdcomfortable: 12pxcompact: 8pxchanges with densitySpacing
space.inset.lg--smbk-space-inset-lgcomfortable: 12pxcompact: 8pxchanges with densitySpacing
border-width.hairline--smbk-border-width-hairline1pxBorder width
color.border.subtle--smbk-color-border-subtlelight: #c4cdd8dark: #2d3237changes with themeColor
radius.container.sm--smbk-radius-container-sm8pxRadius
color.surface.default--smbk-color-surface-defaultlight: #f3f5f8dark: #181a1cchanges with themeColor
color.text.default--smbk-color-text-defaultlight: #232527dark: #e7ebf0changes with themeColor
space.inline.lg--smbk-space-inline-lgcomfortable: 12pxcompact: 8pxchanges with densitySpacing
size.icon.2xl--smbk-size-icon-2xl40pxSize
color.icon.subtle--smbk-color-icon-subtlelight: #4e555edark: #c4cdd8changes with themeColor
space.stack.xs--smbk-space-stack-xs4pxSpacing
typography.body-sm-medium--smbk-typography-body-sm-medium-font-family--smbk-typography-body-sm-medium-font-size--smbk-typography-body-sm-medium-font-weight--smbk-typography-body-sm-medium-line-height--smbk-typography-body-sm-medium-letter-spacing--smbk-typography-body-sm-medium-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 500lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
space.inline.sm--smbk-space-inline-smcomfortable: 6pxcompact: 4pxchanges with densitySpacing
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
space.inline.xs--smbk-space-inline-xs4pxSpacing
size.icon.sm--smbk-size-icon-sm16pxSize
color.feedback.success.text-on-subtle--smbk-color-feedback-success-text-on-subtlelight: #016317dark: #8ddf90changes with themeColor
color.feedback.success.icon--smbk-color-feedback-success-iconlight: #037a1fdark: #54b85bchanges with themeColor
color.feedback.danger.text-on-subtle--smbk-color-feedback-danger-text-on-subtlelight: #a51d17dark: #febdb2changes with themeColor
color.feedback.danger.icon--smbk-color-feedback-danger-iconlight: #cc0d0ddark: #fe7d6dchanges with themeColor
size.target.comfortable--smbk-size-target-comfortable44pxSize
Manifest
file-row.manifest.json
{
  "name": "file-row",
  "block": "smbk-file-row",
  "summary": "One chosen file: its name, size and state, its progress while it uploads, and what can be done with it.",
  "adapt": "css",
  "axes": {
    "status": {
      "values": [
        "selected",
        "uploading",
        "complete",
        "failed",
        "rejected"
      ],
      "default": "selected",
      "description": "Selected is chosen and waiting; uploading shows the spinner and the progress bar; complete has gone through; failed did not, and can be tried again; rejected was not added, and says why."
    }
  },
  "flags": {},
  "parts": [
    "top",
    "icon",
    "text",
    "name",
    "stem",
    "extension",
    "meta",
    "size",
    "separator",
    "state",
    "state-icon",
    "message",
    "actions",
    "progress"
  ],
  "figmaNodeId": "7110:252"
}