Appearance
@vibe-labs/design-components-uploads
Upload component tokens, styles, and TypeScript types for the Vibe Design System. Covers drop zones, file lists, per-file items with status tracking, and a top-bar upload indicator.
Installation
css
@import "@vibe-labs/design-components-uploads";ts
import { UploadItemStatuses } from "@vibe-labs/design-components-uploads/types";
import type { UploadZoneStyleProps, UploadItemStyleProps, UploadIndicatorStyleProps } from "@vibe-labs/design-components-uploads/types";Contents
Tokens (upload.css)
CSS custom properties defined in @layer vibe.tokens:
Drop Zone
| Token | Default | Description |
|---|---|---|
--upload-zone-border-width | 2px | Border width |
--upload-zone-border-style | dashed | Border style |
--upload-zone-border-color | --border-default | Default border |
--upload-zone-border-color-active | --color-accent | Dragover/hover border |
--upload-zone-radius | --radius-lg | Border radius |
--upload-zone-bg | transparent | Background |
--upload-zone-bg-active | --overlay-tint | Dragover background |
--upload-zone-padding | --space-8 | Inner padding |
--upload-zone-min-height | 10rem | Minimum height |
--upload-zone-color | --text-muted | Icon/label color |
--upload-zone-icon-size | 2.5rem | Icon dimensions |
File List
| Token | Default |
|---|---|
--upload-list-gap | --space-2 |
--upload-list-max-height | 24rem |
File Item
| Token | Default |
|---|---|
--upload-item-bg | --surface-elevated |
--upload-item-border-color | --border-subtle |
--upload-item-border-width | --border-width-1 |
--upload-item-radius | --radius-md |
--upload-item-padding | --space-3 |
--upload-item-gap | --space-3 |
--upload-thumb-size | 2.5rem |
--upload-thumb-radius | --radius-sm |
Status Colors
| Token | Default | Status |
|---|---|---|
--upload-status-queued | --text-disabled | Waiting in queue |
--upload-status-pending | --text-muted | Ready to start |
--upload-status-uploading | --color-accent | In progress |
--upload-status-complete | --color-success | Done |
--upload-status-error | --color-danger | Failed |
Top-Bar Indicator
| Token | Default |
|---|---|
--upload-indicator-size | 2rem |
--upload-indicator-bg | --surface-elevated |
--upload-indicator-border-color | --border-subtle |
--upload-indicator-radius | --radius-full |
--upload-indicator-shadow | --shadow-sm |
Generated Styles (upload.g.css)
Component classes generated into @layer vibe.components.
Drop Zone
- upload-zone — dashed border flex container with hidden file input overlay
- dragover modifier — accent border + tinted background
- compact modifier — inline row layout with reduced padding
- Slots:
upload-zone-icon,upload-zone-label,upload-zone-browse(accent underlined link),upload-zone-hint(muted subtext) - Focus-within ring, disabled state
File List
- upload-list — scrollable flex column (24rem max height)
- unbounded modifier — removes max height
File Item
- upload-item — flex row with thumbnail, content, and trailing actions
- upload-item-thumb — square image/icon preview slot
- upload-item-content — flex column with name and meta
- upload-item-name — truncated semibold filename
- upload-item-meta — muted size/type info
- upload-item-trail — trailing action slot
- upload-item-remove — danger-hover remove button
- upload-item-status — status icon colored by scoped
--_upload-status-color - upload-item-progress — 2px bottom bar with fill driven by
--progress-value
Status States
Status variant on upload-item sets --_upload-status-color used by both the status icon and progress fill. Error status also changes the item border.
Top-Bar Indicator
- upload-indicator — pill-shaped clickable status widget
- upload-indicator-spinner — inline border spinner
- upload-indicator-progress — conic-gradient circular progress with
::afterdonut punch-out - complete / error modifiers — success/danger colored border and text
TypeScript Types (types/)
ts
UploadItemStatuses // ["queued", "pending", "uploading", "complete", "error"]
type UploadItemStatus
interface UploadZoneStyleProps { dragover?: boolean; compact?: boolean; disabled?: boolean }
interface UploadListStyleProps { unbounded?: boolean }
interface UploadItemStyleProps { status?: UploadItemStatus }
interface UploadIndicatorStyleProps { complete?: boolean; error?: boolean }
// + zone icon/label/browse/hint, item thumb/content/name/meta/trail/remove/status/progress interfacesDist Structure
| File | Description |
|---|---|
index.css | Barrel — imports tokens + generated styles |
upload.css | Token definitions |
upload.g.css | Generated component styles |
index.js / index.d.ts | TypeScript types + runtime constants |
Dependencies
Requires tokens from @vibe-labs/design (colors, surfaces, borders, spacing, typography, transitions, elevation, overlays). Uses spinner tokens from @vibe-labs/design-components-spinners for indicator spinner.
Build
bash
npm run buildUsage Guide
Import
css
@import "@vibe-labs/design-components-uploads";ts
import { UploadItemStatuses } from "@vibe-labs/design-components-uploads/types";
import type { UploadZoneStyleProps, UploadItemStyleProps } from "@vibe-labs/design-components-uploads/types";Variants
| Attribute / Flag | Values | Notes |
|---|---|---|
data-dragover | boolean flag on upload-zone | Accent border + tinted bg |
data-compact | boolean flag on upload-zone | Inline row layout |
data-disabled | boolean flag on upload-zone | Disabled state |
data-unbounded | boolean flag on upload-list | Removes 24rem max-height |
data-status | queued pending uploading complete error | On upload-item |
data-complete | boolean flag on upload-indicator | Success border/text |
data-error | boolean flag on upload-indicator | Danger border/text |
Progress fill inside upload-item-progress is driven by --progress-value.
Examples
Basic drop zone
html
<div class="upload-zone">
<input type="file" style="display:none" />
<div class="upload-zone-icon" aria-hidden="true">↑</div>
<div class="upload-zone-label">Drag and drop files here</div>
<div class="upload-zone-hint">or</div>
<button type="button" class="upload-zone-browse">Browse files</button>
</div>Drop zone with dragover state
html
<div class="upload-zone" data-dragover>
<input type="file" style="display:none" />
<div class="upload-zone-icon" aria-hidden="true">↑</div>
<div class="upload-zone-label">Drop files to upload</div>
</div>Compact drop zone
html
<div class="upload-zone" data-compact>
<input type="file" style="display:none" />
<div class="upload-zone-icon" aria-hidden="true">↑</div>
<span class="upload-zone-label">Drop files or</span>
<button type="button" class="upload-zone-browse">browse</button>
<span class="upload-zone-hint">Max 10MB</span>
</div>File list with status items
html
<div class="upload-list">
<!-- Uploading -->
<div class="upload-item" data-status="uploading">
<div class="upload-item-thumb"></div>
<div class="upload-item-content">
<div class="upload-item-name">document.pdf</div>
<div class="upload-item-meta">2.4 MB · PDF</div>
</div>
<div class="upload-item-status" aria-hidden="true">↑</div>
<div class="upload-item-progress" style="--progress-value: 65"></div>
</div>
<!-- Complete -->
<div class="upload-item" data-status="complete">
<div class="upload-item-thumb"></div>
<div class="upload-item-content">
<div class="upload-item-name">photo.jpg</div>
<div class="upload-item-meta">1.1 MB · JPEG</div>
</div>
<div class="upload-item-status" aria-hidden="true">✓</div>
<div class="upload-item-trail">
<button type="button" class="upload-item-remove" aria-label="Remove">✕</button>
</div>
</div>
<!-- Error -->
<div class="upload-item" data-status="error">
<div class="upload-item-thumb"></div>
<div class="upload-item-content">
<div class="upload-item-name">corrupt.zip</div>
<div class="upload-item-meta">Failed to upload</div>
</div>
<div class="upload-item-status" aria-hidden="true">!</div>
<div class="upload-item-trail">
<button type="button" class="upload-item-remove" aria-label="Remove">✕</button>
</div>
</div>
</div>Queued and pending items
html
<div class="upload-list">
<div class="upload-item" data-status="queued">
<div class="upload-item-content">
<div class="upload-item-name">waiting.txt</div>
<div class="upload-item-meta">Queued</div>
</div>
</div>
<div class="upload-item" data-status="pending">
<div class="upload-item-content">
<div class="upload-item-name">ready.txt</div>
<div class="upload-item-meta">Ready</div>
</div>
</div>
</div>Top-bar upload indicator
html
<!-- Uploading -->
<div class="upload-indicator">
<div class="upload-indicator-spinner"></div>
<span>3 uploading</span>
</div>
<!-- With circular progress -->
<div class="upload-indicator" style="--progress-value: 60">
<div class="upload-indicator-progress"></div>
<span>60%</span>
</div>
<!-- Complete -->
<div class="upload-indicator" data-complete>
<span>All done</span>
</div>
<!-- Error -->
<div class="upload-indicator" data-error>
<span>Upload failed</span>
</div>With Vue
vue
<template>
<div>
<div
class="upload-zone"
:data-dragover="isDragOver || undefined"
:data-compact="compact || undefined"
@dragover.prevent="isDragOver = true"
@dragleave="isDragOver = false"
@drop.prevent="onDrop"
>
<input type="file" ref="fileInput" style="display:none" @change="onFileSelect" multiple />
<div class="upload-zone-icon" aria-hidden="true">↑</div>
<div class="upload-zone-label">Drag files here or</div>
<button type="button" class="upload-zone-browse" @click="fileInput?.click()">browse</button>
</div>
<div class="upload-list">
<div
v-for="file in files"
:key="file.id"
class="upload-item"
:data-status="file.status"
>
<div class="upload-item-content">
<div class="upload-item-name">{{ file.name }}</div>
<div class="upload-item-meta">{{ file.size }}</div>
</div>
<div class="upload-item-status" aria-hidden="true"></div>
<div v-if="file.status === 'uploading'" class="upload-item-progress" :style="{ '--progress-value': file.progress }"></div>
<div class="upload-item-trail">
<button type="button" class="upload-item-remove" @click="remove(file.id)" aria-label="Remove">✕</button>
</div>
</div>
</div>
</div>
</template>