A file-upload field with a tappable upload affordance, file-name display, and progress/error states.
boder → border token typo (library-wide). Normalize hasLabel to true/false. Split state="Uploaded with thumbnail" into state=uploaded + hasThumbnail: Bool. Rename "Upload error" → error (remove space). Adopt a Figma Slot for the thumbnail image. Add disabled state.Contexts are illustrative. Final screens will reference actual GCash patterns. Upload File appears in forms requiring document proof (KYC, insurance claims, verification).
hasLabel uses yes/no, state="Upload error" contains a space, and "Uploaded with thumbnail" is really an orthogonal boolean, not a state. Also the boder typo in every border token. C2 C3icon-placeholder pattern we've flagged in Chip, Tab Item, List Item Asset) — should be a Figma Slot so product teams can drop in any preview image. C6| 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 |
- Property naming issues.
hasLabelusesyes/noinstead oftrue/false;state="Upload error"contains a space;state="Uploaded with thumbnail"mixes two orthogonal dimensions (state + thumbnail presence). C2 · Variant & Property Naming - Token typo —
boder. Every border token is misspelled:main/upload-file/color/default/boder,main/upload-file/color/error/boder. Rename across the whole collection. C3 · Token Coverage - Thumbnail color hardcoded. Uses
#0057E4withopacity: 5%baked in instead of a token. C3 · Token Coverage - No disabled / pressed / focused states. Forms need a disabled variant for read-only views; pressed and focused are expected interaction affordances. C5 · Interaction State Coverage
- Thumbnail is a placeholder. 52×52 gray block instead of a swappable image slot. Should be a Figma Slot that accepts a real image instance. C6 · Asset & Icon Quality
- Lottie dependency. Progress bar requires the Lottie animation (
0a1cb540-b53a-4e28-afa5-8aa5ca7ebaa1) to be bundled with the native package. Document as a required asset. C6 · Asset & Icon Quality - Code Connect mappings not registered. Blocked until state/property restructure and token rename land. C7 · Code Connect Linkability
- Restructure the state property :
•state: default / uploading / uploaded / error(4 values, clean enums)
•hasThumbnail: Bool(orthogonal — can combine withuploaded)
•hasLabel: Bool(true/false)
•disabled: Bool(new)
Collapses 10 variants into 4 states × 2 hasLabel × 2 hasThumbnail × 2 disabled = 32 prop combinations with no invalid states. Property - Fix the
bodertypo across the token collection. Rename bothdefault/boderanderror/boder. Library-wide change — affects every variant. Token - Tokenize the thumbnail placeholder — replace hardcoded
#0057E4 @ 5%withmain/upload-file/color/default/thumbnail-bg. Token - Adopt a Figma Slot for the thumbnail — swappable preview image. Maps to
@ViewBuilder/@Composableslot for Code Connect. Slot - Reuse Labeled Field for the label + subtext scaffolding. Today Upload File reimplements the label-above + subtext-below pattern that Labeled Field already ships. Making Upload File an input slot inside Labeled Field reduces duplication. Composition
- Document the Lottie dependency — Progress bar is a Lottie animation. Native packages must bundle the animation JSON. Consider replacing with a native progress bar for a lighter dependency. Docs
Empty state with paperclip + "Attach file / photo" placeholder text. 2px border, white bg. Subtext below lists accepted formats.
| Role | Token | Token | Value |
|---|---|---|---|
| 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 |
Shows file name + 5px-tall Lottie progress bar + percentage. Height grows to 91px to accommodate the progress row.
| 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 |
File name (<code>GCash_File.png</code>) + trailing trash icon for removal.
| 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 |
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>.
Red 2px border + red error subtext ("Maximum file size: 20MB").
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") }
| Figma Property | SwiftUI | Compose |
|---|---|---|
| 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 |
// 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" )
| Requirement | iOS | Android |
|---|---|---|
| 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 |
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.
| 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. |
5 state × 2 hasLabel = 10 variants. Clean matrix — every combination exists.
| State | hasLabel | Count |
|---|---|---|
| Default | yes + no | 2 |
| Uploading | yes + no | 2 |
| Uploaded | yes + no | 2 |
| Upload error | yes + no | 2 |
| Uploaded with thumbnail | yes + no | 2 |
hasLabel=yes/no, state="Upload error" has a space, "Uploaded with thumbnail" is orthogonal to the state axis. Openboder. Library-level rename needed. Open#0057E4 @ 5% not tokenized. Open