Skip to content

@vibe-labs/design-vue-progress

Vue 3 progress indicators for the Vibe Design System. Linear bars, radial rings, auto-color thresholds, shimmer effects, and ETA estimation.

Installation

ts
import { VibeProgressBar, VibeProgressRing } from "@vibe-labs/design-vue-progress";

Requires the CSS layer from @vibe-labs/design-components-progress. The package also ships side-effect CSS (shimmer, inside labels, ring spin) imported automatically from progress-extras.css.


Components

VibeProgressBar

Linear progress bar with label positions, shimmer, stripes, and auto-color.

Usage

vue
<!-- Basic -->
<VibeProgressBar :value="65" />

<!-- With title and above label -->
<VibeProgressBar :value="65" title="Upload" label="above" />

<!-- Inside label -->
<VibeProgressBar :value="80" label="inside" size="lg" />

<!-- Right label -->
<VibeProgressBar :value="42" label="right" />

<!-- Custom label format -->
<VibeProgressBar :value="750" :max="1000" label="above" :format-label="(v, min, max) => `${v} / ${max} MB`" />

<!-- Sizes -->
<VibeProgressBar :value="50" size="xs" />
<VibeProgressBar :value="50" size="sm" />
<VibeProgressBar :value="50" size="md" />
<VibeProgressBar :value="50" size="lg" />

<!-- Color variants -->
<VibeProgressBar :value="80" variant="success" />
<VibeProgressBar :value="50" variant="warning" />
<VibeProgressBar :value="20" variant="danger" />

<!-- Auto-color (changes color based on value) -->
<VibeProgressBar :value="progress" variant="auto" />

<!-- Custom auto thresholds -->
<VibeProgressBar
  :value="progress"
  variant="auto"
  :auto-thresholds="[
    { max: 25, color: 'var(--progress-fill-danger)' },
    { max: 75, color: 'var(--progress-fill-warning)' },
    { max: 100, color: 'var(--progress-fill-success)' },
  ]"
/>

<!-- Custom color -->
<VibeProgressBar :value="65" color="#7c3aed" />

<!-- Indeterminate -->
<VibeProgressBar indeterminate />

<!-- Striped -->
<VibeProgressBar :value="70" striped />

<!-- Animated stripes -->
<VibeProgressBar :value="70" striped animated />

<!-- Shimmer effect -->
<VibeProgressBar :value="70" shimmer />

<!-- Custom min/max -->
<VibeProgressBar :value="3" :min="0" :max="10" label="above" />

<!-- Chromeless (for composing segmented bars) -->
<VibeProgressBar :value="40" chromeless />

Props

PropTypeDefaultDescription
valuenumber0Current value
minnumber0Minimum
maxnumber100Maximum
sizeProgressSize"md"xs · sm · md · lg
variantProgressVariant"accent"accent · success · warning · danger · auto
colorstringCustom CSS color (overrides variant)
autoThresholdsAutoColorThreshold[]danger→warning→successThresholds for variant="auto"
labelProgressLabelPosition"none"above · inside · right · none
labelTextstringCustom label text
formatLabel(value, min, max, percentage) => stringLabel format function
titlestringField title above the bar
indeterminatebooleanfalseLoading state
stripedbooleanfalseStriped pattern
animatedbooleanfalseAnimate stripes (requires striped)
shimmerbooleanfalseShimmer/liquid leading edge
chromelessbooleanfalseNo border-radius or background
ariaLabelstring"Progress"Accessible label

VibeProgressRing

Radial/circular progress indicator with centre label.

Usage

vue
<!-- Basic -->
<VibeProgressRing :value="72" />

<!-- Sizes -->
<VibeProgressRing :value="72" size="sm" />
<VibeProgressRing :value="72" size="lg" />
<VibeProgressRing :value="72" size="xl" />

<!-- Custom stroke width -->
<VibeProgressRing :value="72" :stroke-width="4" />

<!-- Auto-color -->
<VibeProgressRing :value="progress" variant="auto" />

<!-- Custom color -->
<VibeProgressRing :value="72" color="#7c3aed" />

<!-- Custom label -->
<VibeProgressRing :value="3" :max="10" :format-label="(v, min, max) => `${v}/${max}`" />

<!-- Label slot -->
<VibeProgressRing :value="72">
  <template #default="{ percentage, formatted }">
    <strong>{{ formatted }}</strong>
  </template>
</VibeProgressRing>

<!-- No label -->
<VibeProgressRing :value="72" :show-label="false" />

<!-- Indeterminate spinner -->
<VibeProgressRing indeterminate />

Props

PropTypeDefaultDescription
valuenumber0Current value
minnumber0Minimum
maxnumber100Maximum
sizeProgressRadialSize"md"sm · md · lg · xl
variantProgressVariant"accent"accent · success · warning · danger · auto
colorstringCustom CSS color
autoThresholdsAutoColorThreshold[]Thresholds for auto variant
strokeWidthnumberStroke width in px
showLabelbooleantrueShow centre label
labelTextstringCustom label text
formatLabel(value, min, max, percentage) => stringLabel format function
indeterminatebooleanfalseSpinner mode
ariaLabelstring"Progress"Accessible label

Scoped Slot

PropertyTypeDescription
valuenumberClamped value
percentagenumberPercentage 0–100
formattedstringFormatted label string

Composables

useProgress(options)

Shared computation for percentage, clamping, and auto-color resolution. Used internally by both components.

ts
import { useProgress } from "@vibe-labs/design-vue-progress";

const { percentage, clampedValue, resolvedColor } = useProgress({
  value: toRef(props, "value"),
  min: toRef(props, "min"),
  max: toRef(props, "max"),
  variant: toRef(props, "variant"),
  color: toRef(props, "color"),
  autoThresholds: toRef(props, "autoThresholds"),
});

useProgressTimer(options)

ETA estimation for long-running operations. Tracks progress samples over time, computes a smoothed rate, and estimates time remaining.

ts
import { useProgressTimer } from "@vibe-labs/design-vue-progress";

const { percentage } = useProgress({ value, min, max });
const { state, reset } = useProgressTimer({ percentage });

// state.value.etaFormatted → "~2m 30s"
// state.value.rate         → 0.65 (% per second)
// state.value.completionTime → Date
// state.value.reliable     → true (enough samples)

Options

OptionTypeDefaultDescription
percentageRef<number>requiredReactive percentage (0–100)
sampleSizenumber10Samples to keep for smoothing
minSamplesnumber3Minimum samples before showing estimate

State

PropertyTypeDescription
etanumber | nullEstimated seconds remaining
etaFormattedstring | nullFormatted ETA (e.g. "~2m 30s")
ratenumber | nullProgress rate (% per second)
reliablebooleanEnough samples collected
completionTimeDate | nullPredicted completion time

Auto-Color Thresholds

The variant="auto" mode changes the bar/ring color based on the current percentage. Default thresholds:

RangeColor
0–33%var(--progress-fill-danger) (red)
34–66%var(--progress-fill-warning) (yellow)
67–100%var(--progress-fill-success) (green)

Override with the autoThresholds prop for custom breakpoints.


Dependencies

PackagePurpose
@vibe-labs/design-components-progressCSS tokens + generated styles

Build

bash
npm run build

Built with Vite + vite-plugin-dts. Outputs ES module with TypeScript declarations. Note: sideEffects: ["*.css"] is set in package.json to ensure the extras CSS is not tree-shaken.


Usage Guide

Setup

ts
import { VibeProgressBar, VibeProgressRing } from "@vibe-labs/design-vue-progress";
import "@vibe-labs/design-components-progress";

VibeProgressBar — Practical Examples

File Upload Progress

vue
<script setup lang="ts">
import { ref } from "vue";
import { VibeProgressBar } from "@vibe-labs/design-vue-progress";

const uploadProgress = ref(0);
const uploadName = ref("");

async function uploadFile(file: File) {
  uploadName.value = file.name;
  uploadProgress.value = 0;

  const formData = new FormData();
  formData.append("file", file);

  // Simulated progress — replace with XHR onprogress
  const interval = setInterval(() => {
    if (uploadProgress.value < 95) uploadProgress.value += 5;
  }, 200);

  await fetch("/api/upload", { method: "POST", body: formData });
  clearInterval(interval);
  uploadProgress.value = 100;
}
</script>

<template>
  <div>
    <input type="file" @change="(e) => uploadFile((e.target as HTMLInputElement).files![0])" />
    <VibeProgressBar
      :value="uploadProgress"
      :title="uploadName"
      label="above"
      variant="auto"
      shimmer
    />
  </div>
</template>

Segmented Multi-Step Progress

vue
<script setup lang="ts">
import { computed, ref } from "vue";
import { VibeProgressBar } from "@vibe-labs/design-vue-progress";

const steps = ["Validate", "Process", "Finalise"];
const currentStep = ref(0);
const stepProgress = ref(0); // 0–100 within current step

const totalPercent = computed(() =>
  ((currentStep.value / steps.length) * 100) + (stepProgress.value / steps.length)
);
</script>

<template>
  <VibeProgressBar
    :value="totalPercent"
    label="above"
    :format-label="(v) => `Step ${currentStep + 1} of ${steps.length}: ${steps[currentStep]}`"
    variant="accent"
    striped
    animated
  />
</template>

Storage Quota Indicator

vue
<script setup lang="ts">
import { VibeProgressBar } from "@vibe-labs/design-vue-progress";

const usedBytes = 7_500_000_000;
const totalBytes = 10_000_000_000;

function formatBytes(b: number) {
  return `${(b / 1e9).toFixed(1)} GB`;
}
</script>

<template>
  <VibeProgressBar
    :value="usedBytes"
    :max="totalBytes"
    label="above"
    variant="auto"
    :format-label="(v, _min, max) => `${formatBytes(v)} of ${formatBytes(max)} used`"
    :auto-thresholds="[
      { max: 60, color: 'var(--progress-fill-success)' },
      { max: 85, color: 'var(--progress-fill-warning)' },
      { max: 100, color: 'var(--progress-fill-danger)' },
    ]"
  />
</template>

VibeProgressRing — Practical Examples

Dashboard Metric Ring

vue
<script setup lang="ts">
import { VibeProgressRing } from "@vibe-labs/design-vue-progress";

const cpuUsage = 72;
</script>

<template>
  <VibeProgressRing
    :value="cpuUsage"
    size="xl"
    variant="auto"
  >
    <template #default="{ percentage }">
      <div class="ring-label">
        <span class="ring-label__value">{{ Math.round(percentage) }}%</span>
        <span class="ring-label__unit">CPU</span>
      </div>
    </template>
  </VibeProgressRing>
</template>

Loading Indicator Ring

vue
<template>
  <div v-if="loading" class="loading-center">
    <VibeProgressRing indeterminate size="lg" aria-label="Loading content" />
  </div>
</template>

Composables — Usage Example

vue
<script setup lang="ts">
import { ref } from "vue";
import { useProgress, useProgressTimer } from "@vibe-labs/design-vue-progress";
import { VibeProgressBar } from "@vibe-labs/design-vue-progress";

const value = ref(0);
const min = ref(0);
const max = ref(100);

const { percentage } = useProgress({ value, min, max });
const { state } = useProgressTimer({ percentage });

// Simulate progress
setInterval(() => { if (value.value < 100) value.value += 1; }, 300);
</script>

<template>
  <VibeProgressBar :value="value" :max="max" label="above" shimmer />
  <p v-if="state.reliable">ETA: {{ state.etaFormatted }}</p>
</template>

Common Patterns

Indeterminate While Loading, Determinate on Progress

vue
<template>
  <VibeProgressBar
    :value="progress"
    :indeterminate="progress === 0"
    label="above"
    variant="accent"
  />
</template>

Ring with Fraction Label

vue
<template>
  <VibeProgressRing
    :value="completed"
    :max="total"
    :format-label="(v, _min, max) => `${v}/${max}`"
    size="lg"
  />
</template>

Vibe