File upload
File upload lets people drag and drop or browse for files and see each file's progress. Use it wherever people add files.
Beta until design confirms the drag-over, disabled and invalid states that code adds, and Figma is re-bound to local tokens (C40); the focus indicator (C5) is also open. Everything else on this page is implemented and tested.
Preview
Change the controls, then drop or browse for a file. The preview renders the real dropzone and reports what it received. Nothing on this page uploads: files stay in your browser.
import { FileUpload } from '@nuton/ui'
<FileUpload
hint="PDF, DOCX, or PPTX, up to 20 MB."
accept=".pdf,.docx,.pptx"
multiple
onFilesSelected={(files) => startUploads(files)}
onFilesRejected={(files) => explainRejection(files)}
/>Installation
Availability
@nuton/ui and @nuton/tokens are private workspace packages. They aren't published to a registry yet (D4: registry deferred; D12: distribution waits on the provenance review). Inside the Nuton monorepo, add them as workspace dependencies. The commands below apply once the packages are published.
1. Add the packages
pnpm add @nuton/ui @nuton/tokens @remixicon/reactnpm install @nuton/ui @nuton/tokens @remixicon/reactyarn add @nuton/ui @nuton/tokens @remixicon/reactPeer dependencies: react and react-dom 18 or later. @remixicon/react supplies the upload, status and action icons (D1).
2. Load tokens and component styles once
// app/layout.tsx (or your app's entry file)
import '@nuton/tokens/tokens.css' // --nu-* design tokens
import '@nuton/ui/styles.css' // component styles (uses the tokens)3. Bring your own upload code
The components don't send files anywhere. FileUpload hands you the chosen File objects; your code uploads them and passes each file's status and progress to FileUploadItem. Map --nu-font-body to your loaded Inter font as described on the Button page.
Usage
import { useState } from 'react'
import { FileUpload, FileUploadItem, FileUploadList } from '@nuton/ui'
type Upload = { id: string; file: File; progress: number; status: 'uploading' | 'completed' | 'error' }
export function LessonMaterials({ upload }: { upload: (file: File, onProgress: (p: number) => void) => Promise<void> }) {
const [uploads, setUploads] = useState<Upload[]>([])
const update = (id: string, patch: Partial<Upload>) =>
setUploads((list) => list.map((u) => (u.id === id ? { ...u, ...patch } : u)))
const start = (files: File[]) =>
files.forEach((file) => {
const id = crypto.randomUUID()
setUploads((list) => [...list, { id, file, progress: 0, status: 'uploading' }])
upload(file, (progress) => update(id, { progress }))
.then(() => update(id, { status: 'completed' }))
.catch(() => update(id, { status: 'error' }))
})
return (
<>
<FileUpload hint="PDF, DOCX, or PPTX, up to 20 MB." accept=".pdf,.docx,.pptx" multiple onFilesSelected={start} />
<FileUploadList aria-label="Lesson materials">
{uploads.map((u) => (
<FileUploadItem
key={u.id}
fileName={u.file.name}
fileSize={u.file.size}
status={u.status}
progress={u.progress}
onRemove={() => setUploads((list) => list.filter((x) => x.id !== u.id))}
/>
))}
</FileUploadList>
</>
)
}FileUploadonly reports files throughonFilesSelected. Upload state lives in your code;FileUploadItemjust displays it.- Always pass a
hintthat matchesacceptand your size limit. There's no default, because only you know the rules. acceptalso filters dropped files. HandleonFilesRejectedand tell people why a file wasn't added.- The component doesn't check file size. Check it yourself and show the reason with
status="error"andstatusText. - Give every item an
onRemove, and anonCancelwhile uploading if your upload can be stopped. No handler means no button.
Variants and sizes
File upload is four parts: the FileUpload dropzone, a FileUploadItem card per file, FileUploadList to stack them, and the FileFormatIcon inside each card. Items come in two densities; the format icon has types, colours, sizes and two appearances.
Dropzone text and icon
The title and button label have English defaults from Figma. Override them to translate, or to fit a specific upload such as a space cover image.
<FileUpload
title="Add a cover image for your space"
hint="JPEG or PNG, at least 1200 × 600 px, up to 5 MB."
browseLabel="Choose image"
icon={<RiImageAddLine />}
accept="image/jpeg,image/png"
onFilesSelected={setCover}
/>Item text
The size reads from fileSize, or “uploaded of total” when you also pass bytesUploaded. sizeText and statusText replace the defaults, for example with a specific error.
- cell-structure.pdf
- week-3-reading.docx
- field-trip-recording.mp4
- quiz-answers
<FileUploadList aria-label="Item text examples">
<FileUploadItem fileName="cell-structure.pdf" fileSize={1_240_000} bytesUploaded={496_000} progress={40} />
<FileUploadItem fileName="week-3-reading.docx" status="completed" fileSize={86_000} />
<FileUploadItem fileName="field-trip-recording.mp4" status="error" fileSize={48_000_000} statusText="File too large. The limit is 20 MB." />
<FileUploadItem fileName="quiz-answers" status="completed" sizeText="12 questions" formatLabel="QUIZ" formatColor="teal" />
</FileUploadList>These items have no handlers, so they show no buttons. Sizes use Figma's units (B, KB, MB) in 1024 steps. The number is formatted in English by default; pass locale to format the digits for another language.
Translated strings
<FileUploadItem
fileName="cours-photosynthese.pdf"
fileSize={2_400_000}
bytesUploaded={1_800_000}
progress={75}
locale="fr"
lang="fr"
labels={{
uploading: 'Envoi en cours…',
sizeOf: (uploaded, total) => `${uploaded} sur ${total}`,
}}
/>Set lang on the item, or a parent, when its strings differ from the page language. The remaining strings (completed, error, retry, cancel, remove) are listed under FileUploadItemLabels.
Compact card
density="compact" is Figma's attached-file card (File Upload Cards [1.1], 117:1055): 10px padding, a red delete button, and only the size once the file is uploaded (“Completed” is still announced to screen readers). Use it for files already attached to a lesson or message. URLs work too: YouTube and web links get their own icons.
- Design System! Ep. 1.pptx
- https://youtu.be/dQw4w9WgXcQ
- https://nuton.app/guides/photosynthesis
- lab-results.xlsx
<FileUploadList aria-label="Lesson materials">
<FileUploadItem density="compact" fileName="Design System! Ep. 1.pptx" status="completed" fileSize={122_880} onRemove={remove} />
<FileUploadItem density="compact" fileName="https://youtu.be/dQw4w9WgXcQ" status="completed" sizeText="Video · 12:04" onRemove={remove} />
<FileUploadItem density="compact" fileName="https://nuton.app/guides/photosynthesis" status="completed" sizeText="Web page" onRemove={remove} />
<FileUploadItem density="compact" fileName="lab-results.xlsx" fileSize={2_400_000} bytesUploaded={900_000} progress={38} onCancel={cancel} />
</FileUploadList>These examples have no handlers, so they show no buttons. With onRemove, the compact card shows Figma's red delete button.
Format icon types
Figma's File Format 2 has seven types: a coloured badge with short text, or an icon for YouTube and web links, in a soft tile: 36px in Figma, 40px by default in code (P4). FileUploadItem picks the type from the file name or URL with getFileFormat; use FileFormatIcondirectly in your own lists. Six more types are code-only, for files Figma doesn't cover.
// Figma types
<FileFormatIcon type="pdf" />
<FileFormatIcon type="ppt" />
<FileFormatIcon type="xls" />
<FileFormatIcon type="doc" />
<FileFormatIcon type="txt" />
<FileFormatIcon type="youtube" />
<FileFormatIcon type="link" />
// Code-only types (FC-21): pass the extension as the label
<FileFormatIcon type="image" label="PNG" />
<FileFormatIcon type="video" label="MP4" />
<FileFormatIcon type="audio" label="MP3" />
<FileFormatIcon type="archive" label="ZIP" />
<FileFormatIcon type="code" label="JSON" />
<FileFormatIcon type="file" /> // unknown, no extensionFormat icon sizes and appearance
40 is the default (P4) and 36 is Figma's size. 24, 32 and 48 keep the same proportions (badge two-thirds of the tile). appearance="plain"drops the tile and lets the badge fill the box, like Figma's inner “File Format” badge; use it at 24 and 32, where the tile leaves the text too small.
4840 · default36 · Figma3224plain 32plain 24plain 24<FileFormatIcon type="pdf" size={48} />
<FileFormatIcon type="pdf" /> // 40, the default
<FileFormatIcon type="pdf" size={36} /> // Figma
<FileFormatIcon type="pdf" size={32} />
<FileFormatIcon type="pdf" size={24} />
<FileFormatIcon type="pdf" size={32} appearance="plain" />
<FileFormatIcon type="pdf" size={24} appearance="plain" />
<FileFormatIcon type="youtube" size={24} appearance="plain" />Format icon colours and custom badges
Each type has a colour; color, label and icon override it, for example a quiz or a flashcard deck. Badges hold up to four characters; four-character text is set slightly smaller.
redcrimsonorangeyellowgreentealbluepurplepinkgray<FileFormatIcon label="QUIZ" color="teal" />
<FileFormatIcon label="CARD" color="purple" />
<FileFormatIcon icon={<RiMicLine />} color="pink" />
// Every colour
{['red', 'crimson', 'orange', 'yellow', 'green', 'teal', 'blue', 'purple', 'pink', 'gray'].map((c) => (
<FileFormatIcon key={c} label="ABC" color={c} />
))}| Colour | Token | White text | Used by |
|---|---|---|---|
red | red-700 (Figma State/Error/Base, 4.15:1) | 5.26:1 | PDF (Figma) |
crimson | red-800 (Figma Error/700) | 6.98:1 | YouTube (Figma) |
orange | orange-800 (Figma State/Warning/Base, 3.44:1) | 4.93:1 | PPT (Figma) |
green | green-800 (Figma Success/700, 4.29:1) | 5.72:1 | XLS (Figma) |
blue | State/Primary/Base | 5.59:1 | DOC (Figma) |
gray | gray-600 (Figma State/Faded/Base, 4.23:1) | 6.69:1 | TXT, web link (Figma); archive, other files |
purple | purple-600 (code-only) | 5.84:1 | Images |
pink | pink-700 (code-only) | 4.97:1 | Video |
teal | teal-800 (code-only) | 5.54:1 | Audio, code |
yellow | yellow-900 (code-only) | 5.34:1 | Not used by a type; pass it yourself |
The icon is decorative: the file name next to it carries the meaning, so the badge's small white text isn't held to text contrast. Code-only colours use a darker shade so the text stays legible. They, the extra sizes and the plain appearance need design confirmation (FC-21).
Dimensions
| Part | Size | Spacing | Shape and type |
|---|---|---|---|
| Dropzone | Fills its container (400 in Figma); 202px tall with a hint | Padding 32, gap 20, text gap 6 | Radius 12, 1px dashed Stroke/Sub-300; title Label/Small, hint Paragraph/X Small |
| Upload icon | 24 × 24 | — | Icon/Sub-600 |
| Browse button | 32px tall | Padding 6 / 12 (8 plus a 4px text inset) | Radius 8, Label/Small |
| Item | 72px (completed, or error without retry); 94px uploading; 100px error with retry | Padding 16 (14 at the start), row gap 12, text gap 4, 16 above the bar | Radius 12, 1px Stroke/Soft-200 (error: State/Error/Base) |
| Item action | 24 × 24, 20px icon | — | Radius 6 |
| Status icon | 16 × 16 | 4 to its text | Paragraph/X Small |
| List | — | 12 between items | — |
| Compact item | 60px (completed; 56 in Figma with its 36px icon) | Padding 10, row gap 12 | Radius 12, 1px Stroke/Soft-200; red delete icon |
| Format icon 40 (default, P4) | 40 × 40 tile, 26.7 × 26.7 badge | Padding 6.7 | Tile radius 8.9; badge radius 4.4; text about 8.5px bold, icons 15.6px |
| Format icon 36 (Figma) | 36 × 36 tile, 24 × 24 badge | Padding 6 | Tile Bg/Weak-50 radius 8; badge radius 4; text about 7.7px bold, icons 14px |
| Format icon 24–48 | Tile N, badge 2N/3 (plain: badge N) | Padding N/6 | Tile radius 2N/9, badge radius 1/6 of the badge (code-only) |
Taken from the component CSS, which follows Figma's geometry. The 12px list gap is a code choice: Figma's 24px is the spacing of its documentation canvas, not a spec (pending a decision).
States and interactions
Dropzone
Drag-over is also a code addition (C40): Figma defines only Default and Hover. Hover styles apply only on devices with hover.
Item
onRetry is set. Press it to watch a simulated retry.Status changes are announced politely. The item action has no focus state in Figma, so code uses the button focus effects (C40, C5). The error card's delete uses Figma's dark-surface style, whose hover is invisible on white; code uses the ghost hover instead (C40).
State tokens
| Token | Default | Changes in |
|---|---|---|
--nu-file-upload-bg / -bg-hover | Bg/White / Bg/Weak-50 | Hover and drag-over use the hover fill |
--nu-file-upload-border | Stroke/Sub-300 (dashed) | Invalid: State/Error/Base. Drag-over: solid Stroke/Strong-950 |
--nu-file-upload-item-border | Stroke/Soft-200 | Error: State/Error/Base |
--nu-file-upload-item-status-icon | State/Primary/Base | Completed: State/Success/Base. Error: State/Error/Base |
Figma binds the card's spinner and progress to a remote blue/500; code uses the local State/Primary/Base (blue/600) and the local Progress bar, following C20 (C40).
Composition examples
Lesson materials
The full flow: add files, watch them upload, cancel, remove and retry. Press “Add example files” or pick your own. Every third file fails once at 55%; files over 20 MB fail straight away with a reason and no retry. Uploads are simulated with timers.
Removing an item removes the focused button. Move focus somewhere sensible, here back to the browse button, so keyboard users aren't sent to the top of the page.
Inside a Field
Wrap the dropzone in Field for a label, hint and error. The browse button's name becomes “Lab report Browse File”, the hint and error describe it, and an error sets the invalid border. Submit without a file to see the error.
The hidden file input resets after each pick and has no name, so a native form post can't send the file. Upload from your own code (pending a decision).
API reference
FileUpload props
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | 'Choose a file or drag & drop it here.' | Heading. Part of the browse button’s description. |
hint | ReactNode | — | Constraint text under the title, added to the button's description. No default: it must match accept. |
browseLabel | ReactNode | 'Browse File' | Label of the browse button. |
icon | ReactNode | <RiUploadCloud2Line /> | Replaces the decorative upload icon. Rendered aria-hidden. |
accept | string | — | Native accept filter, for example ".pdf,image/*". Also applied to dropped files. |
multiple | boolean | false | Allows several files. Without it, only the first dropped file is accepted and the rest are rejected. |
disabled | boolean | false | Disables the picker and ignores drops. Inherits from a surrounding Field. |
invalid | boolean | false | Error border. Inherits from a surrounding Field with an error. |
capture | boolean | 'user' | 'environment' | — | Native capture hint for mobile cameras. |
onFilesSelected | (files: File[]) => void | — | Called with accepted files from the picker or a drop. Nothing is uploaded. |
onFilesRejected | (files: File[]) => void | — | Called with dropped files that fail accept or exceed the multiple limit. |
id | string | — | Id of the browse button (the focusable control). A surrounding Field sets it. |
ref | Ref<HTMLDivElement> | — | Forwarded to the root dropzone element. |
…rest | Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'onDrop' | 'onChange' | 'children'> | — | Other attributes, passed to the root. onClick and the drag handlers run before the built-in ones; aria-describedby is added to the button's description. |
FileUploadItem props
| Prop | Type | Default | Description |
|---|---|---|---|
fileNamerequired | string | — | File name. Also names the progress bar and the action buttons. |
status | 'uploading' | 'completed' | 'error' | 'uploading' | Upload state (Figma State). |
progress | number | 0 | Upload progress, 0–100. Shown only while uploading. |
fileSize | number | — | Total size in bytes, formatted with formatFileSize. |
bytesUploaded | number | — | Bytes sent so far. With fileSize, the size reads “uploaded of total”. |
sizeText | ReactNode | — | Replaces the size text entirely. |
statusText | ReactNode | — | Replaces the status text, for example a specific error. |
density | 'default' | 'compact' | 'default' | Card layout. compact is Figma 117:1055: 10px padding, red delete, size only once uploaded. |
formatType | FileFormatType | from file name or URL | Format icon type, from getFileFormat(fileName). |
formatLabel | string | from getFileFormat | Format badge text (“report.docx” → DOC). |
formatColor | FileFormatColor | from getFileFormat | Format badge colour. |
icon | ReactNode | <FileFormatIcon /> | Replaces the format icon, for example with a thumbnail. Rendered aria-hidden. |
onCancel | () => void | — | X button while uploading. Falls back to onRemove. No handler, no button. |
onRemove | () => void | — | Delete button when completed or failed. No handler, no button. |
onRetry | () => void | — | Shows “Try again” in the error state and calls this. |
labels | Partial<FileUploadItemLabels> | English | Overrides any of the strings below. |
locale | string | string[] | 'en' | Locale for size formatting. English by default so server and browser render the same text; pass your app's locale to localise. |
ref | Ref<HTMLDivElement> | — | Forwarded to the card element. |
…rest | Omit<HTMLAttributes<HTMLDivElement>, 'children'> | — | Other attributes, passed to the card (className, lang, data-*…). |
FileUploadItemLabels
| Prop | Type | Default | Description |
|---|---|---|---|
uploading | string | 'Uploading...' | Status text while uploading. |
completed | string | 'Completed' | Status text when done. |
error | string | 'Failed' | Status text on failure. |
retry | string | 'Try again' | Retry action label. |
cancel | string | 'Cancel upload' | Name prefix of the X button; the file name is appended. |
remove | string | 'Remove' | Name prefix of the delete button; the file name is appended. |
sizeOf | (uploaded: string, total: string) => string | `${uploaded} of ${total}` | Joins uploaded and total size. |
FileUploadList props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | The items. Each child is wrapped in an <li>; null and false are skipped. |
ref | Ref<HTMLUListElement> | — | Forwarded to the list. |
…rest | HTMLAttributes<HTMLUListElement> | — | Other attributes, passed to the <ul>. Give it an aria-label when the page has several lists. |
FileFormatIcon props
| Prop | Type | Default | Description |
|---|---|---|---|
type | FileFormatType | 'file' | Figma Type: pdf, ppt, xls, doc, txt, youtube, link; code-only image, video, audio, archive, code, file. Sets the default text, colour and icon. |
label | string | the type's text | Badge text, up to 4 characters, such as “PDF”. |
color | 'red' | 'crimson' | 'orange' | 'yellow' | 'green' | 'teal' | 'blue' | 'purple' | 'pink' | 'gray' | the type's colour | Badge colour. |
icon | ReactNode | YouTube and globe icons for those types | Shows an icon in the badge instead of text. |
size | 24 | 32 | 36 | 40 | 48 | 40 | Box size in px. 40 is the default (P4), 36 is Figma; the rest keep its proportions. |
appearance | 'tile' | 'plain' | 'tile' | tile: the Figma Weak-50 square around the badge. plain: the badge alone, filling the box. |
ref | Ref<HTMLSpanElement> | — | Forwarded to the root. |
…rest | Omit<HTMLAttributes<HTMLSpanElement>, 'color' | 'children'> | — | Other attributes, passed to the root. It's always aria-hidden, whatever you pass. |
Helpers
Plain functions with no React or browser dependencies, so they also run on the server.
| Function | Signature | Behaviour |
|---|---|---|
fileMatchesAccept | (file: Pick<File, 'name' | 'type'>, accept?: string) => boolean | True when the file matches an accept string: extensions (.pdf), wildcards (image/*) and exact MIME types. An empty accept matches everything. |
formatFileSize | (bytes: number, locale?: string | string[]) => string | Locale-aware size in 1024 steps, one decimal below 10. Negative or non-finite input reads as 0. |
getFileExtension | (fileName: string) => string | The extension, upper-cased and cut to 4 characters. Empty when there's none. |
getFileFormat | (fileNameOrUrl: string) => FileFormat | The type, badge text and colour for a file name or URL. YouTube URLs → youtube, other URLs → link; Figma types use their short text (DOCX → DOC); others show the extension. |
getFileFormatColor | (fileName: string) => FileFormatColor | The colour part of getFileFormat. Unknown extensions get gray. |
FILE_FORMAT_TYPES | Record<FileFormatType, { label, color }> | Each type's default text and colour. |
import { fileMatchesAccept, formatFileSize, getFileExtension, getFileFormat } from '@nuton/ui'
fileMatchesAccept({ name: 'notes.pdf', type: 'application/pdf' }, '.pdf,.docx') // true
fileMatchesAccept({ name: 'photo.png', type: 'image/png' }, '.pdf,.docx') // false
formatFileSize(4_400_000) // "4.2 MB"
formatFileSize(4_400_000, 'fr') // "4,2 Mo"
getFileExtension('lesson-plan.docx') // "DOCX"
getFileFormat('lesson-plan.docx') // { type: 'doc', label: 'DOC', color: 'blue' }
getFileFormat('https://youtu.be/abc') // { type: 'youtube', label: '', color: 'crimson' }| File name | getFileExtension | getFileFormat | Matches ".pdf,.pptx" |
|---|---|---|---|
photosynthesis-slides.pptx | "PPTX" | ppt · "PPT" · orange | true |
lab-safety-checklist.pdf | "PDF" | pdf · "PDF" · red | true |
leaf-cross-section.png | "PNG" | image · "PNG" · purple | false |
https://youtu.be/dQw4w9WgXcQ | "BE/D" | youtube · "" · crimson | false |
reading-list | "" | file · "" · gray | false |
| Bytes | formatFileSize(bytes, 'en') |
|---|---|
0 | 0 B |
900 | 900 B |
389,000 | 380 KB |
4,400,000 | 4.2 MB |
2,147,483,648 | 2 GB |
Styling hooks
The dropzone renders .nu-file-upload with data-dragging, data-disabled and data-invalid; the item renders .nu-file-upload-item with data-status; the icon renders .nu-file-format-icon with data-color and data-size. Use props rather than styling these. The tokens in State tokens are the supported override points.
Accessibility
Keyboard
| Key | Behaviour |
|---|---|
| Tab | Moves to the browse button, the dropzone’s only tab stop, then through each item’s Try again and cancel or delete buttons. |
| Enter / Space | On the browse button, opens the native file picker. On an item button, runs it. |
Screen readers
The browse button is named by its label (inside a Field: “field label + browse label”) and described by the title, hint and any Field hint or error. Each item's status sits in a polite live region; its progress bar is named by the file name; its buttons read “Cancel upload file” or “Remove file”; Try again is described by the file name. Icons are hidden. The list is a real list.
Focus
The browse button and item buttons use Figma's button focus effects, below 3:1 today (C5); the item button has no Figma focus state (C40). Removing an item doesn't move focus for you.
Contrast and colour
The dashed border is 1.53:1 (the C7 pair) and the card and button borders 1.19:1, below 3:1. The error border and icon are 4.15:1, above 3:1; the Try again and Delete text use Red 700, 5.26:1 (C6). Every format badge is at least 4.5:1 with its white text: Figma's red, orange, green and gray move one step darker (C51). The file name is always text. Status is shown with icon and words, never colour alone.
Touch and pointer
The whole dropzone opens the picker on click or tap. Item buttons are 24 × 24, the WCAG 2.5.8 minimum; Try again is 20px tall (C28). Drag and drop isn't available on most touch screens, so the browse button is the main path. capture can offer the camera.
You must: give a hint that states the allowed formats and size; explain rejected and failed files in words; move focus after removing an item; pass onRemove (and onCancel where possible) so people can undo a choice.
Figma mapping
| Figma component · property | Figma values | Code |
|---|---|---|
| File Upload Area · State | Default · Hover | FileUpload; hover is interactive. Drag-over, disabled and invalid are code additions (C40) |
| File Upload Area · text | Title, constraint text, Browse File | title, hint, browseLabel |
| File Upload Cards · State | In Progress · Success · Error | FileUploadItem status: uploading · completed · error |
| File Upload Cards · Progress Bar | Instance | progress (the local Progress component) |
| File Upload Cards · Link Button “Try again” | Error only | onRetry |
| File Upload Cards · Compact Button | close-line · delete-bin-line | onCancel, onRemove |
| File Format 2 · Type (4474:61041) | PDF · PPT · XLS · DOCS · TXT · YOUTUBE · WEB Links | FileFormatIcon type: pdf · ppt · xls · doc · txt · youtube · link (plus code-only types) |
| File Format 2 · tile | 36 × 36 | size={36}, appearance="tile" (other sizes and plain are code-only) |
| File Upload Cards [1.1] (117:1055) | Attached-file card | FileUploadItem density="compact" |
| Image Upload | 57:16317 | Not built (C40) |
Every binding on these components is remote. Code uses the same-valued local tokens, except the card's blue (see State tokens). The remote library calls green “Primary” and blue “Secondary”, the reverse of local naming, so Figma should be re-bound (C40).
Guidelines and related components
Use File upload for
- Adding lesson materials, readings or recordings to a space.
- Students submitting assignments, such as a lab report.
- Importing a file to work from, such as notes to turn into flashcards.
Do and don't
Common mistakes
- Expecting the component to upload, or to post the file with a form. It only reports files; your code sends them.
- Ignoring
onFilesRejected, so a dropped file silently disappears. - Setting
acceptwithout a matchinghint, or a hint that promises formatsacceptblocks. - Removing an item without moving focus, which sends keyboard users back to the top of the page.
- Wrapping the dropzone in another button or link. The browse button is already the control.