KeepReady
Upload File Component link

A file-upload field with a tappable upload affordance, file-name display, and progress/error states.

Keep — all findings resolved
Rebuilt on node 4853:26511 in the 2026 Working File, and the headline recommendation has landed: the conflated state property is now Status = Default | Uploading | Error | Uploaded × State = Default | Focused | Disabled, the two-axis split §6 of the Property Naming Guidelines prescribes, with the four unsupported combinations documented as unreachable. Thumbnail-Slot is a real Figma Slot on a token-bound placeholder, the label and subtext rows reuse the shared FormGroup Header and Subtext Message instances, the file row is cleanly named throughout, the boder token typo is fixed, and the Lottie dependency behind the Uploading status is documented including its failure fallback. All four DS Health traits pass; the only item still open is Code Connect, blocked until the native library exists.
In Context

Contexts are illustrative. Final screens will reference actual GCash patterns. Upload File appears in forms requiring document proof (KYC, insurance claims, verification).

Upload Docs Attach file / photo ID_proof.jpg Submit
Live Preview
Attach file / photoAccepted format: JPEG, PNG, or PDF, Up to 3 MB
Properties
state
hasLabel
DS Health
Reusable
Pass
Used in KYC flows, insurance claims, profile setup — anywhere a user uploads a document or photo. Status covers the full upload lifecycle — Default, Uploading, Uploaded and Error.
Self-contained
Pass
Carries its own bg, border, padding and radius, and composes shared FormGroup Header and Subtext Message instances for labelling and helper copy. The progress-bar GIF is a Figma-preview device only — native drives the platform progress primitive from real upload percentage, with no asset to bundle.
Consistent
Pass
The property model is exemplary — Status and State on separate axes, the split §6 of the Property Naming Guidelines prescribes — with the four unsupported combinations documented as unreachable rather than undrawn. The file row, the Figma Slot and the shared scaffolding instances all follow convention; the two internal container frames keep their original names as a recorded exception.
Composable
Pass
The thumbnail is a real Figma SLOT (Thumbnail-Slot), so product teams can drop in a file preview without detaching, and the component composes shared FormGroup Header and Subtext Message instances for its label and helper rows.
Behavior
State iOS Android Figma Property Notes
Default Yes Yes state=Default Empty input with paperclip + "Attach file / photo" label
Uploading Yes Yes state=Uploading Shows file name + Lottie progress bar + percentage
Uploaded Yes Yes state=Uploaded File name + trailing trash icon to remove
Uploaded with thumbnail Yes Yes state=Uploaded with thumbnail 52×52 image preview + truncated file name + trash. Should be orthogonal hasThumbnail prop.
Upload error Yes Yes state=Upload error Red 2px border + red subtext ("Maximum file size: 20MB")
Disabled / Pressed / Focused N/A N/A Not defined. Engineers must improvise. C5
Issues
  • Code Connect mappings not registered. Blocked — no native library exists yet. The property schema is clean and every layer is semantically named, so mapping is a mechanical step once the library lands. C7 · Code Connect Linkability
  • Property naming cleaned up. v2.0: Rebuilt on node 4853:26511 in the 2026 Working File. Upload errorError (no space), Uploaded with thumbnail split so thumbnail presence is no longer folded into the state value, and hasLabel is gone — the label is now an always-present FormGroup Header instance. (C2) C2 · Variant & Property Naming
  • Label and subtext scaffolding reused. v2.0: Every variant composes a shared FormGroup Header above and a Subtext Message below, matching Text Area. The label, helper copy and error message are no longer re-authored per component. (Composition) C1 · Layer Structure & Naming
  • Axis renamed StateStatus. v2.1: Uploading, Uploaded and Error are process outcomes rather than interaction states, so Status is the correct axis per the Property Naming Guidelines. Closes the half-applied state restructure. (C2 · Property) C2 · Variant & Property Naming
  • Disabled variant added. v2.1: A fifth variant covers the read-only case that KYC review and locked submission screens need — previously there was no canonical appearance for it. Added on the Status axis rather than a separate State axis; see open issues for the consequence. (C5) C5 · Interaction State Coverage
  • Error variant structure normalised. v2.1: Status=Error now wraps its input-field in an Attach File - Input frame (6526:105078) like the other four variants. Previously it was the only one missing that layer, which made the tree inconsistent across the set. Its filename layer was also renamed #nameFileName. (C1) C1 · Layer Structure & Naming
  • State split onto its own axis. v2.2: Status = Default | Uploading | Uploaded | Error × State = Default | Disabled, authored as a deliberately sparse sparse matrix. Disabled × Uploading and Disabled × Error are intentionally left unauthored — an upload in flight is already non-interactive, and an error you can't act on is a dead end. Process outcome and interaction state are now cleanly separated, and Pressed / Focused have somewhere to live. (C2 · Property) C2 · Variant & Property Naming
  • Disabled + Uploaded variant authored. v2.2: 6572:111324 covers the locked review screen showing an already-attached document — the case the single-axis set couldn't express. Muted container, muted filename, and the subtext dims with it. The trash icon is muted rather than removed, a deliberate choice to signal that deletion returns when the form unlocks. (C5) C5 · Interaction State Coverage
  • Thumbnail converted to a Figma Slot. v2.2: Icon Placeholder is now Thumbnail-Slot, a real SLOT node (6572:111570) carrying an icon-placeholder rectangle as its default content. Product teams can drop a real file preview in without detaching, and the hardcoded placeholder colour is now slot default rather than baked component chrome. Closes the placeholder, slot-adoption and thumbnail-token findings together. (C6 · Slot) C6 · Asset & Icon Quality
  • Error progress bar removed. v2.2: Status=Error no longer renders a 20% progress bar against its "Maximum file size: 20MB" message — a file rejected for size never starts uploading, so the two contradicted. Variant height dropped 167 → 148 to match. (C1) C1 · Layer Structure & Naming
  • label wrapper renamed to FileRow. v2.2: The frame holding the thumbnail, attach icon and filename is now FileRow rather than label, which had described none of its contents. (C1) C1 · Layer Structure & Naming
  • Progress-bar layer names cleaned up. v2.3: 🟢 https://app.lottiefiles.com/animation/0a1cb540-…progress-fill, and the progress percentage text #labelProgressValue. The filename wrapper also went nameFileNameWrapper. Layer naming is now clean apart from container. The asset behind progress-fill is a separate finding — see open issues. (C1) C1 · Layer Structure & Naming
  • Focused state added. v2.4: State = Default | Focused | Disabled, with Focused authored against Status=Default (6572:111764) and Status=Uploaded (6572:111782) — the same two pairings Disabled carries, and for the same reason. Eight variants in the sparse matrix. Completes interaction-state coverage, since Pressed is deliberately out of scope. (C5) C5 · Interaction State Coverage
  • Layer naming complete across all eight variants. v2.5: The wrapper frame is now exactly UploadField in all eight variants — verified by exact string match, with no trailing whitespace and no leftover container. An intermediate pass had left three different names for the same frame, including five with an invisible trailing space that would have broken name-based override matching. Together with FileRow, FileName, FileNameWrapper, ProgressValue and Thumbnail-Slot, every layer in the set now carries a correct, consistent semantic name. (C1) C1 · Layer Structure & Naming
  • Token namespace resolved — generic tokens applied. v2.4: Closed by owner confirmation. The component-scoped main/upload-file/color/* namespace was dropped in favour of the shared generic token scale, which retires the boder misspelling along with it — there is no longer a per-component tier carrying the typo. Same direction Search Field and Text Area took. (C3 · Token) C3 · Token Coverage
  • Progress-bar GIF confirmed preview-only. v2.4: Closed by owner decision — the animated GIF fill on progress-fill exists so the Figma variant previews motion, not as a handoff asset. Native implementations use the platform progress primitive (SwiftUI ProgressView(value:), Compose LinearProgressIndicator(progress =)) driven by real upload percentage; the Figma fill is never exported. Worth stating in the handoff notes so an implementer doesn't mistake it for a required asset. (C6) C6 · Asset & Icon Quality
  • Pressed ruled out of scope. v2.2: Closed by owner decision — Upload File will not carry a Pressed variant. Tapping the field opens the system file picker, and both platforms supply their own touch feedback for that transition, so a DS-defined pressed appearance would add a variant without adding information. Focused remains in scope; it carries accessibility weight that Pressed does not. (C5) C5 · Interaction State Coverage
  • State property restructured onto two axes. v2.4: Verified on the live node. The single conflated axis is now Status = Default | Uploading | Error | Uploaded × State = Default | Focused | Disabled — system report and user interaction on separate properties, exactly the split §6 of the Property Naming Guidelines now prescribes. This is the recommendation applied. (C2 · Property) C2 · Variant & Property Naming
  • Thumbnail is a real Figma Slot. v2.4: Thumbnail-Slot (6572:111553) is a genuine SLOT node, not a frame standing in for one, and its name is kebab-case per §4. A consumer swaps their own thumbnail in without detaching, and native handoff has a real content slot to bind. (C1 · Slot) C1 · Layer Structure & Naming
  • Label and subtext scaffolding reuses the shared components. v2.4: The row above the field is a FormGroup Header instance and the row below is a Subtext Message instance — the same two Upload File’s siblings use, rather than redrawn locally. A change to either propagates across Form Elements for free. (C4 · Composition) C4 · Native Mappability
  • File row layer naming cleaned up. v2.4: FileRow, FileNameWrapper, FileName and TrailingAction all read as PascalCase semantic names, and the trailing control is a real Trash icon instance rather than a drawn glyph. (C1) C1 · Layer Structure & Naming
  • Container frame names accepted as-is. v2.5: Attach File - Input and input-field keep their current names by owner decision. They are internal structural frames rather than slots or text layers, nothing binds to them, and renaming carried more churn than the consistency was worth. Recorded so a later reviewer reads them as a settled exception rather than a missed pass. (C1) C1 · Layer Structure & Naming
  • Unsupported variant combinations documented. v2.5: Eight of twelve combinations ship, and the four gaps are deliberate. Uploading has no Focused or Disabled because a field mid-transfer is not interactive — the control is busy, not available. Error has no Focused or Disabled because the error state is what a user focuses in order to fix, so it collapses back to Default the moment they do, and a disabled field cannot have failed an upload it was never able to start. Only Default and Uploaded carry the full interaction range. Native implementations should treat the missing combinations as unreachable rather than undrawn. (C5) C5 · Interaction State Coverage
  • boder token typo corrected. v2.5: Fixed in the token collection on owner confirmation, before Code Connect could bind generated native constants to the misspelling. Attested rather than verified — token names are not readable through the review tooling. (C3 · Token) C3 · Token Coverage
  • Thumbnail placeholder confirmed token-bound. v2.5: The placeholder inside Thumbnail-Slot fills #EEF2F9, the shared surface value used across Form Elements rather than a local hex. Confirmed by the owner; variable bindings are not readable through the review tooling, so this is attested. (C3 · Token) C3 · Token Coverage
  • Lottie dependency documented. v2.5: The Uploading status is driven by a Lottie animation the static component cannot depict — the file row holds its layout while the animation plays in place of the thumbnail. It loops for the duration of the transfer and is replaced, not stopped, when the status moves to Uploaded or Error. If the animation fails to load, implementations should fall back to the platform’s indeterminate progress indicator rather than an empty slot, so the field never reads as idle while a transfer is running. Progress is indeterminate by design: the component reports that an upload is happening, not how far along it is. (Docs) C4 · Native Mappability
Styles
Default — empty state
DES DEV

Empty state with paperclip + "Attach file / photo" placeholder text. 2px border, white bg. Subtext below lists accepted formats.

Properties
state
hasLabel
hasThumbnail
disabled
Properties
state Default
Variant Default — empty state
hasLabel no
hasThumbnail false
disabled false
Colors
Bg #FFFFFF
Border #D7E0EF
Text #0A2757
Placeholder #90A8D0
Layout
Field height 48px
Padding H 12px
Border radius radius/radius-2 (6px)
Upload icon 20 × 20
Typography
Label style Primary/Label/Light/Small
Label font Proxima Soft Semibold · 14 / 14
Colors by State
Role Token TokenValue
Default bg main/upload-file/color/default/bg #FFFFFF
border main/upload-file/color/default/boder typo #E5EBF4
leading icon main/upload-file/color/default/icon-leading #6780A9
trailing icon main/upload-file/color/default/icon-trailing #005CE5
label main/upload-file/color/default/label #0A2757
file name main/upload-file/color/default/label-name #005CE5
progress label main/upload-file/color/default/progress-label #0A2757
thumbnail bg — (hardcoded #0057E4 @ 5%) not tokenized
Error bg main/upload-file/color/error/bg #FFFFFF
border main/upload-file/color/error/boder typo #D61B2C
leading icon main/upload-file/color/error/icon-leading #6780A9
label main/upload-file/color/error/label #0A2757
file name main/upload-file/color/error/label-name #005CE5
error subtext main/subtext-message/error/label #D61B2C
Subtext default label main/subtext-message/primary/label #6780A9
Uploading — Lottie progress
DES DEV

Shows file name + 5px-tall Lottie progress bar + percentage. Height grows to 91px to accommodate the progress row.

Properties
state
hasLabel
hasThumbnail
disabled
Properties
state Uploading
Variant Uploading — Lottie progress
hasLabel no
hasThumbnail false
disabled false
Colors
Bg #FFFFFF
Border #D7E0EF
Text #0A2757
Placeholder #90A8D0
Layout
Field height 48px
Padding H 12px
Border radius radius/radius-2 (6px)
Upload icon 20 × 20
Typography
Label style Primary/Label/Light/Small
Label font Proxima Soft Semibold · 14 / 14
Layout
Role Token Value
Container width 304px
Input height (default/uploaded/error) 72px
Input height (uploading) 91px (adds progress row)
Border width 2px
Corner radius radius/radius-2 6px
Horizontal padding 16px (12L / 16R for thumbnail)
Vertical padding 24px
Icon → name gap space/space-4 4px
Thumbnail size 52 × 52
Thumbnail → name gap space/space-8 8px
Label → input gap space/space-8 8px
Input → subtext gap space/space-8 8px
Progress bar height 5px
Progress bar width 250px
Leading / trailing icon size 24 × 24
Uploaded — file name + trash
DES DEV

File name (<code>GCash_File.png</code>) + trailing trash icon for removal.

Properties
state
hasLabel
hasThumbnail
disabled
Properties
state Uploaded
Variant Uploaded — file name + trash
hasLabel no
hasThumbnail false
disabled false
Colors
Bg #FFFFFF
Border #D7E0EF
Text #0A2757
Placeholder #90A8D0
Layout
Field height 48px
Padding H 12px
Border radius radius/radius-2 (6px)
Upload icon 20 × 20
Typography
Label style Primary/Label/Light/Small
Label font Proxima Soft Semibold · 14 / 14
Typography
Role Token Spec
Label Primary/Label/Light/Small Proxima Soft Semibold · 14 / 14 · +0.25
File name / placeholder Primary/Label/Light/Large Proxima Soft Semibold · 18 / 18 · +0.25
Subtext Secondary/Bold/Caption BarkAda Semibold · 12 / 18
Progress percentage Secondary/Bold/Small Caption BarkAda Semibold · 10 / 15
Uploaded with thumbnail — preview + name
DES DEV

52×52 thumbnail preview + truncated file name (<code>New_GCash_Fi….jpeg</code>) + trash. Recommended to split into <code>state=uploaded</code> + <code>hasThumbnail: true</code>.

Properties
state
hasLabel
hasThumbnail
disabled
Properties
state Uploaded with thumbnail
Variant Uploaded with thumbnail — preview + name
hasLabel no
hasThumbnail true
disabled false
Colors
Bg #FFFFFF
Border #D7E0EF
Text #0A2757
Placeholder #90A8D0
Layout
Field height 48px
Padding H 12px
Border radius radius/radius-2 (6px)
Upload icon 20 × 20
Typography
Label style Primary/Label/Light/Small
Label font Proxima Soft Semibold · 14 / 14
Upload error — red border + error subtext
DES DEV

Red 2px border + red error subtext ("Maximum file size: 20MB").

Properties
state
hasLabel
hasThumbnail
disabled
Properties
state Error
Variant Upload error — red border + error subtext
hasLabel no
hasThumbnail false
disabled false
Colors
Bg #FFFFFF
Border #D61B2C
Text #0A2757
Placeholder #90A8D0
Layout
Field height 48px
Padding H 12px
Border radius radius/radius-2 (6px)
Upload icon 20 × 20
Typography
Label style Primary/Label/Light/Small
Label font Proxima Soft Semibold · 14 / 14
Installation Planned API

iOS — Swift Package Manager

// In Xcode: File → Add Package Dependencies
"https://github.com/AY-Org/eb-ds-ios"

// Requires: lottie-ios for progress animation
"https://github.com/airbnb/lottie-ios"

Android — Gradle (Kotlin DSL)

dependencies {
    implementation("com.eastblue.ds:upload-file:1.0.0")
    // Requires: lottie-compose for progress animation
    implementation("com.airbnb.android:lottie-compose:6.4.0")
}
Property Mapping
Figma PropertySwiftUICompose
state=Default/Uploading/Uploaded/Upload error state: EBUploadState state: .default / .uploading / .uploaded / .error
state=Uploaded with thumbnail state=uploaded + hasThumbnail .hasThumbnail(true)
hasLabel=yes/no label: String? label: String?
fileName: String? fileName: String?
progress: Double progress: Double (0.0–1.0)
thumbnail placeholder Figma Slot → ViewBuilder @ViewBuilder thumbnail
disabled: Bool .disabled(true)
onSelect / onRemove onSelect / onRemove
SwiftUI
ios/Components/UploadFile/EBUploadFile.swift
Jetpack Compose
android/components/uploadfile/EBUploadFile.kt
Usage Snippets Planned API
Usage
// Default — empty state
EBUploadFile(label: "Proof of ID", onSelect: { url in
    // handle picked file
})

// Uploading
EBUploadFile(fileName: "GCash_File.png", progress: 0.2)
    .ebState(.uploading)

// Uploaded with thumbnail (Figma Slot)
EBUploadFile(fileName: "ID_proof.jpg", onRemove: { ... }) {
    AsyncImage(url: imageURL)
        .aspectRatio(contentMode: .fill)
        .clipShape(RoundedRectangle(cornerRadius: 4))
}
.ebState(.uploaded)

// Error
EBUploadFile(label: "Upload receipt",
    errorMessage: "Maximum file size: 20MB")
    .ebState(.error)
// Default — empty state
EBUploadFile(
    label = "Proof of ID",
    onSelect = { uri -> /* handle picked file */ }
)

// Uploading
EBUploadFile(
    state = EBUploadState.Uploading,
    fileName = "GCash_File.png",
    progress = 0.2f
)

// Uploaded with thumbnail (Figma Slot)
EBUploadFile(
    state = EBUploadState.Uploaded,
    fileName = "ID_proof.jpg",
    onRemove = { /* ... */ }
) {
    AsyncImage(
        model = imageUrl,
        contentDescription = null,
        modifier = Modifier.clip(RoundedCornerShape(4.dp))
    )
}

// Error
EBUploadFile(
    state = EBUploadState.Error,
    label = "Upload receipt",
    errorMessage = "Maximum file size: 20MB"
)
Accessibility
RequirementiOSAndroid
Role .accessibilityAddTraits(.isButton) when empty; announce as "Upload" when actionable Role.Button in semantics
File picked announcement Announce file name after selection via .accessibilityAnnouncement AccessibilityManager.announce()
Progress announcement .accessibilityValue("\(Int(progress * 100)) percent") stateDescription = "$percent percent"
Error announcement Include error message in accessibility label; use .isRejected trait semantics { error(...) }
Remove button Separate accessibility element: .accessibilityLabel("Remove \(fileName)") contentDescription = "Remove $fileName"
Tap target 72px height > 44pt minimum > 48dp minimum
Usage Guidelines

Do

Use the thumbnail slot for image uploads (ID photos, receipts) so users can verify the correct file was picked.

Don't

Show a generic thumbnail placeholder as the final state — either show the real thumbnail or use the plain uploaded state with just the filename.

Do

Always pair the default state with subtext listing accepted formats and size limits so users don't discover constraints only via error state.

Don't

Let users attempt uploads silently only to show an error — preempt format / size violations on the client side.

Do

Use the error state for client-side validation failures (size, format). Show a specific error message indicating what needs to change.

Don't

Use the error state for network failures during upload — those are transient. Show a toast or retry affordance instead.

Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Ready Semantic: input-field, Attach, Trash, Icon Placeholder, upload-file-progress, Subtext Message.
C2 Variant & Property Naming Requires Rework hasLabel yes/no, state has "Upload error" with space, "Uploaded with thumbnail" is orthogonal.
C3 Token Coverage Requires Rework Library-wide boder token typo. Thumbnail bg hardcoded.
C4 Native Mappability Ready Maps to PhotosPicker / DocumentPicker (iOS), GetContent / PickVisualMedia (Android).
C5 Interaction State Coverage Requires Rework No disabled, pressed, or focused states.
C6 Asset & Icon Quality Needs Refinement Thumbnail is a placeholder; Lottie dependency needs documentation.
C7 Code Connect Linkability Needs Refinement Blocked by C2 cleanup.
Variants Inventory (10 total)

5 state × 2 hasLabel = 10 variants. Clean matrix — every combination exists.

StatehasLabelCount
Defaultyes + no2
Uploadingyes + no2
Uploadedyes + no2
Upload erroryes + no2
Uploaded with thumbnailyes + no2
1.0.0 — April 2026Major
Initial Assessment · node 18482:35064
Component assessed — 10 variants (5 state × 2 hasLabel). Lottie progress bar, thumbnail placeholder. Documented
Initial
Property naming issueshasLabel=yes/no, state="Upload error" has a space, "Uploaded with thumbnail" is orthogonal to the state axis. Open
C2 Open
Token typo — All border tokens spelled boder. Library-level rename needed. Open
C3 Open
Thumbnail bg hardcoded#0057E4 @ 5% not tokenized. Open
C3 Open
Missing states — No disabled, pressed, or focused. Open
C5 Open
Thumbnail placeholder + Lottie dependency — Thumbnail is not a slot; Lottie requires asset bundling. Open
C6 Open
Code Connect mappings — Not registered. Open
C7 Open