Skip to content

@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

TokenDefaultDescription
--upload-zone-border-width2pxBorder width
--upload-zone-border-styledashedBorder style
--upload-zone-border-color--border-defaultDefault border
--upload-zone-border-color-active--color-accentDragover/hover border
--upload-zone-radius--radius-lgBorder radius
--upload-zone-bgtransparentBackground
--upload-zone-bg-active--overlay-tintDragover background
--upload-zone-padding--space-8Inner padding
--upload-zone-min-height10remMinimum height
--upload-zone-color--text-mutedIcon/label color
--upload-zone-icon-size2.5remIcon dimensions

File List

TokenDefault
--upload-list-gap--space-2
--upload-list-max-height24rem

File Item

TokenDefault
--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-size2.5rem
--upload-thumb-radius--radius-sm

Status Colors

TokenDefaultStatus
--upload-status-queued--text-disabledWaiting in queue
--upload-status-pending--text-mutedReady to start
--upload-status-uploading--color-accentIn progress
--upload-status-complete--color-successDone
--upload-status-error--color-dangerFailed

Top-Bar Indicator

TokenDefault
--upload-indicator-size2rem
--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 ::after donut 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 interfaces

Dist Structure

FileDescription
index.cssBarrel — imports tokens + generated styles
upload.cssToken definitions
upload.g.cssGenerated component styles
index.js / index.d.tsTypeScript 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 build

Usage 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 / FlagValuesNotes
data-dragoverboolean flag on upload-zoneAccent border + tinted bg
data-compactboolean flag on upload-zoneInline row layout
data-disabledboolean flag on upload-zoneDisabled state
data-unboundedboolean flag on upload-listRemoves 24rem max-height
data-statusqueued pending uploading complete errorOn upload-item
data-completeboolean flag on upload-indicatorSuccess border/text
data-errorboolean flag on upload-indicatorDanger 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>

Vibe