Zetta v1.6

Components · Forms & selection

File input

Drag-and-drop file upload zone with file list and upload progress. Split from zetta-form-v1_4.md (removed in v1.5) in v1.4.

Preview

Drop files here or browse PNG, JPG or PDF up to 10MB
brand-guidelines.pdf 2.4 MB
hero-render.psd Too large

Build with the skill

No package to install — hand this to your AI to generate or migrate the component in your stack.

Prompt for your AI
Apply the Zetta "File input" component to this project using the zetta-design-md skill.

WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `file-input` spec and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `file-input` spec in this project's stack.

AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.

ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility.

Anatomy & rules

╭──────────────────────────────────────╮
│         ↑ upload icon (20px)         │  ← dashed border drop zone
│   Drag files here or click to browse │
│        PNG, JPG up to 5 MB           │  ← helper text
╰──────────────────────────────────────╯
[📄 report.pdf      ×]                    ← file-input-file-item
[████████░░] 80%                          ← file-input-progress
[⚠ image.exe        ×]                    ← file-input-file-item-error
State Border Fill Notes
Default 1px dashed {colors.border-strong} {colors.surface-card}
Hover 1px dashed {colors.border-focus} {colors.primary-bg}
Focus 2px solid {colors.border-focus} outline {colors.surface-card} Keyboard only
Drag over 1px dashed {colors.primary} {colors.primary-bg} Icon shifts to primary
Disabled 1px dashed {colors.disabled-border} {colors.disabled-bg} not-allowed
Error 1px dashed {colors.danger} {colors.surface-card}

Do

  • State accepted file types and size limit in helper text inside the drop zone: "PNG, JPG up to 5 MB."
  • Show upload progress per file once upload starts.
  • Show an error file item (red pill) for rejected files with a reason.
  • Allow click-to-browse as a fallback for users who cannot drag.

Don't

  • Don't hide the file list after upload — users need confirmation of what was uploaded.
  • Don't auto-submit on file selection — let the user review before committing.
  • Drop zone label: "Drag files here or click to browse."
  • Helper text: list accepted types and size limit. "PNG, JPG up to 5 MB." or "PDF only, max 10 MB."
  • File item: show filename + file size. Truncate long names with ellipsis in the middle: "very-long-fi…name.pdf".
  • Error item: show the rejection reason next to the filename. "File too large", "Unsupported format."
  • Remove button aria-label: "Remove [filename]".

Keyboard

Key Behavior
Tab Focus the drop zone
Enter / Space Open the native file browser
Tab (on file item remove button) Focus remove button
Enter / Space (on remove button) Remove file from list

Accessibility

  • Drop zone must be a focusable <button> or <div> with role="button" and tabindex="0".
  • Screen reader instructions: include a visually hidden text explaining drag-and-drop and click-to-browse.
  • aria-live="polite" region announces file additions, removals, and upload progress updates.
  • Each file item's remove button: aria-label="Remove [filename]".
  • Progress bar: role="progressbar" with aria-valuenow, aria-valuemin="0", aria-valuemax="100".
  • Error state: aria-describedby on the drop zone pointing to the error message.

Spec

Values and token references resolved straight from the Zetta spec.

file-input
backgroundColor
{colors.surface-card}
borderColor
{colors.border-strong}
borderWidth
1px
borderStyle
dashed
borderRadius
{rounded.base}
textColor
{colors.muted}
fontFamily
Geist
fontSize
14px
fontWeight
400
lineHeight
1.6
padding
{spacing.md}
display
flex
flexDirection
column
alignItems
center
justifyContent
center
gap
{spacing.sm}
minHeight
96px
cursor
pointer
iconSize
20px
iconColor
{colors.muted}
file-input-hover
borderColor
{colors.border-focus}
backgroundColor
{colors.primary-bg}
file-input-focus
outline
2px solid {colors.border-focus}
outlineOffset
2px
borderColor
{colors.border-focus}
file-input-drag-over
borderColor
{colors.primary}
backgroundColor
{colors.primary-bg}
iconColor
{colors.primary}
file-input-disabled
backgroundColor
{colors.disabled-bg}
borderColor
{colors.disabled-border}
textColor
{colors.disabled-text}
cursor
not-allowed
file-input-error
borderColor
{colors.danger}
file-input-file-list
display
flex
flexDirection
column
gap
{spacing.xs}
marginTop
{spacing.sm}
file-input-file-item
display
flex
alignItems
center
gap
{spacing.sm}
backgroundColor
{colors.surface-secondary}
borderRadius
{rounded.base}
paddingX
12px
paddingY
{spacing.sm}
fontFamily
Geist
fontSize
13px
fontWeight
400
textColor
{colors.ink}
iconSize
16px
iconColor
{colors.muted}
file-input-file-item-error
backgroundColor
{colors.danger-bg}
textColor
{colors.danger-text}
iconColor
{colors.danger-text}
file-input-progress
height
4px
borderRadius
{rounded.full}
trackColor
{colors.surface-muted}
fillColor
{colors.primary}
marginTop
{spacing.xs}