Visual primitive
Vue Progress Bars and Circular Progress
Progress familyAn accessible progress bar for uploads, imports, background jobs, and completion meters.
Playground
Linear progress
Progress playground
Use the linear meter for page-level loading, imports, uploads, and background work with enough horizontal space for a label.
Properties
Control props
Current progress value. Leave empty for indeterminate progress.
Maximum progress value.
Accessible progress label.
Filled bar tone.
Track height.
npm install @getdom/studio<script setup>
import '@getdom/studio/style.css';
import { reactive } from 'vue';
import { DomProgress } from '@getdom/studio';
const data = reactive({
"value": 68,
"max": 100,
"label": "Progress",
"tone": "primary",
"size": "md",
"showValue": true,
"showLabel": true,
"indeterminate": false
});
</script>
<template>
<DomProgress
v-bind="data"
/>
</template>Playground
Segmented progress
Segmented progress playground
An accessible stacked progress bar for workflows that divide a changing total across multiple states.
- 2 completed
- 1 processing
- 1 queued
Properties
Control props
Ordered progress states. Each segment accepts key, label, value, a semantic tone, and an optional CSS colour override.
Completed or otherwise processed item count used by the summary and accessibility value.
Maximum item count. Defaults to the sum of all segments and expands when segment values grow.
Visible summary label and accessible progress label.
Semantic tone used by the percentage summary.
Segmented track height.
npm install @getdom/studio<script setup>
import '@getdom/studio/style.css';
import { reactive } from 'vue';
import { DomSegmentedProgress } from '@getdom/studio';
const data = reactive({
"segments": [
{
"key": "complete",
"label": "completed",
"value": 2,
"tone": "success",
"color": ""
},
{
"key": "processing",
"label": "processing",
"value": 1,
"tone": "warning",
"color": "var(--warning)"
},
{
"key": "queued",
"label": "queued",
"value": 1,
"tone": "neutral",
"color": ""
}
],
"value": 2,
"max": null,
"label": "pages processed",
"tone": "warning",
"size": "lg",
"showSummary": true,
"showPercentage": true,
"showLegend": true,
"showZeroSegments": false
});
</script>
<template>
<DomSegmentedProgress
v-bind="data"
/>
</template>Workflow
Crawl progress with a changing total
Move pages between queued, processing, completed, and failed states. Discovering more pages increases the total and recalculates every segment automatically.
- 18 completed
- 3 processing
- 21 queued
- 2 failed
npm install @getdom/studio<script setup>
import '@getdom/studio/style.css';
import { computed, reactive } from 'vue';
import { DomButton, DomSegmentedProgress } from '@getdom/studio';
const crawl = reactive({
complete: 18,
processing: 3,
queued: 21,
failed: 2,
});
const segments = computed(getSegments);
const processed = computed(getProcessedCount);
const summaryTone = computed(getSummaryTone);
/**
* Builds the ordered crawl-state segments from the current lifecycle counts.
*
* @returns {Array<Record<string, unknown>>} Crawl progress segments.
*/
function getSegments() {
return [
{ key: 'complete', label: 'completed', value: crawl.complete, tone: 'success' },
{ key: 'processing', label: 'processing', value: crawl.processing, tone: 'warning' },
{ key: 'queued', label: 'queued', value: crawl.queued, tone: 'neutral' },
{ key: 'failed', label: 'failed', value: crawl.failed, tone: 'danger' },
];
}
/**
* Counts terminal crawl states as processed pages.
*
* @returns {number} Complete and failed page count.
*/
function getProcessedCount() {
return crawl.complete + crawl.failed;
}
/**
* Resolves the summary tone from the most important active crawl state.
*
* @returns {'warning'|'danger'|'success'|'neutral'} Summary tone.
*/
function getSummaryTone() {
if (crawl.processing > 0) return 'warning';
if (crawl.queued > 0) return 'neutral';
if (crawl.failed > 0) return 'danger';
return 'success';
}
/**
* Adds newly discovered pages to the queued segment and dynamic total.
*
* @returns {void}
*/
function discoverPages() {
crawl.queued += 3;
}
/**
* Moves the next queued page into active processing.
*
* @returns {void}
*/
function startNextPage() {
if (crawl.queued < 1) return;
crawl.queued -= 1;
crawl.processing += 1;
}
/**
* Moves one active page into the completed state.
*
* @returns {void}
*/
function completePage() {
if (crawl.processing < 1) return;
crawl.processing -= 1;
crawl.complete += 1;
}
/**
* Moves one active page into the failed state.
*
* @returns {void}
*/
function failPage() {
if (crawl.processing < 1) return;
crawl.processing -= 1;
crawl.failed += 1;
}
</script>
<template>
<div class="w-full max-w-5xl space-y-5">
<DomSegmentedProgress
:segments="segments"
:value="processed"
:tone="summaryTone"
label="pages processed"
size="lg"
/>
<div class="flex flex-wrap gap-2 border-t border-border pt-4">
<DomButton size="sm" variant="secondary" @click="discoverPages">Discover 3 pages</DomButton>
<DomButton size="sm" variant="secondary" :disabled="crawl.queued < 1" @click="startNextPage">Start next page</DomButton>
<DomButton size="sm" variant="secondary" :disabled="crawl.processing < 1" @click="completePage">Complete page</DomButton>
<DomButton size="sm" variant="danger" :disabled="crawl.processing < 1" @click="failPage">Fail page</DomButton>
</div>
</div>
</template>
Playground
Radial progress
Radial progress playground
A compact circular progress meter for uploads, media processing, sync status, and inline background jobs.
Properties
Control props
Current progress value. Leave empty for an empty meter.
Maximum progress value.
Accessible progress label.
Progress stroke tone.
Rendered diameter. Use a preset or a pixel number.
npm install @getdom/studio<script setup>
import '@getdom/studio/style.css';
import { reactive } from 'vue';
import { DomRadialProgress } from '@getdom/studio';
const data = reactive({
"value": 64,
"max": 100,
"label": "Progress",
"tone": "success",
"size": "md",
"showValue": true
});
</script>
<template>
<DomRadialProgress
v-bind="data"
/>
</template>Composition
Unknown-duration loading
Combine the spinner's separate track and arc colors with an indeterminate progress sweep when work has started but no total is available.
Reading getdom.studio...
npm install @getdom/studio<script setup>
import '@getdom/studio/style.css';
import { DomProgress, DomSpinner } from '@getdom/studio';
</script>
<template>
<div class="w-full max-w-xl rounded-2xl border border-border bg-canvas p-6 shadow-sm">
<div class="mb-4 flex items-center gap-3">
<DomSpinner
label="Reading getdom.studio"
size="md"
background="color-mix(in oklab, var(--warning) 20%, transparent)"
foreground="var(--warning)"
/>
<p class="text-sm font-semibold text-muted-fg">Reading getdom.studio...</p>
</div>
<DomProgress
label="Reading getdom.studio"
:show-label="false"
indeterminate
size="md"
tone="warning"
/>
</div>
</template>
Demo
Status progress bars
Use DomProgress for clear task progress, including indeterminate work that has started but has not reported a value yet.
npm install @getdom/studio<script setup>
import '@getdom/studio/style.css';
import { DomProgress } from '@getdom/studio';
const jobs = [
{
label: 'Uploading images',
value: 72,
tone: 'success',
size: 'md',
},
{
label: 'Encoding video',
value: 42,
tone: 'primary',
size: 'lg',
},
{
label: 'Waiting for import',
value: null,
tone: 'neutral',
size: 'sm',
indeterminate: true,
},
];
</script>
<template>
<div class="w-full max-w-xl space-y-5">
<DomProgress
v-for="job in jobs"
:key="job.label"
:label="job.label"
:value="job.value"
:tone="job.tone"
:size="job.size"
:indeterminate="job.indeterminate"
show-value
/>
</div>
</template>
Demo
Radial meters
Use DomRadialProgress when the progress state needs to sit inside a thumbnail, list row, toolbar, or compact status area.
Image upload
Document sync
Processing warning
Compact transfer
npm install @getdom/studio<script setup>
import '@getdom/studio/style.css';
import { DomRadialProgress } from '@getdom/studio';
const meters = [
{
label: 'Image upload',
value: 86,
tone: 'success',
size: 'lg',
},
{
label: 'Document sync',
value: 52,
tone: 'primary',
size: 'md',
},
{
label: 'Processing warning',
value: 31,
tone: 'warning',
size: 'md',
},
{
label: 'Compact transfer',
value: 68,
tone: 'neutral',
size: 'sm',
showValue: false,
},
];
</script>
<template>
<div class="flex flex-wrap items-center justify-center gap-6">
<div
v-for="meter in meters"
:key="meter.label"
class="flex min-w-28 flex-col items-center gap-3 text-center"
>
<DomRadialProgress
:label="meter.label"
:value="meter.value"
:tone="meter.tone"
:size="meter.size"
:show-value="meter.showValue !== false"
/>
<p class="text-xs font-medium text-muted-fg">{{ meter.label }}</p>
</div>
</div>
</template>
Composition
Upload overlays
Radial progress works well over media previews because it keeps the upload state readable without resizing the item layout.
homepage-hero.jpg
image/jpeg - 78%
brand-guidelines.pdf
application/pdf - 44%
product-gallery.png
image/png - 91%
npm install @getdom/studio<script setup>
import '@getdom/studio/style.css';
import { DomRadialProgress } from '@getdom/studio';
const uploads = [
{
name: 'homepage-hero.jpg',
type: 'image/jpeg',
progress: 78,
tone: 'success',
},
{
name: 'brand-guidelines.pdf',
type: 'application/pdf',
progress: 44,
tone: 'primary',
},
{
name: 'product-gallery.png',
type: 'image/png',
progress: 91,
tone: 'success',
},
];
</script>
<template>
<div class="grid w-full gap-4 sm:grid-cols-3">
<article
v-for="upload in uploads"
:key="upload.name"
class="overflow-hidden rounded-lg border border-border bg-secondary/35 p-3"
>
<div class="relative grid aspect-[4/3] place-items-center overflow-hidden rounded-md bg-canvas/60">
<div class="grid size-14 place-items-center rounded-2xl bg-secondary text-muted-fg ring-1 ring-border">
<svg viewBox="0 0 24 24" class="size-8" fill="none" aria-hidden="true">
<path d="M7 3.75h7l3 3v13.5H7V3.75Zm7 0v3h3M9.5 12.5h5M9.5 15.5h3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
<div class="absolute inset-0 grid place-items-center bg-canvas/50 backdrop-blur-[1px]">
<DomRadialProgress
:label="`${upload.name} upload progress`"
:value="upload.progress"
:tone="upload.tone"
size="lg"
/>
</div>
</div>
<div class="mt-3 min-w-0 text-center">
<p class="truncate text-sm font-semibold text-canvas-fg">{{ upload.name }}</p>
<p class="mt-1 text-xs text-muted-fg">{{ upload.type }} - {{ upload.progress }}%</p>
</div>
</article>
</div>
</template>
Linear reference
Props
Control props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
value | number | number | — | Current progress value. Leave empty for indeterminate progress. |
max | number | number | 100 | Maximum progress value. |
label | string | string | 'Progress' | Accessible progress label. |
tone | 'primary' | 'success' | 'warning' | 'danger' | 'neutral' | string | 'primary' | Filled bar tone. |
size | 'sm' | 'md' | 'lg' | string | 'md' | Track height. |
showValue | boolean | boolean | false | Show a percentage label beside the title. |
showLabel | boolean | boolean | true | Show the label above the bar. The label remains accessible when visually hidden. |
indeterminate | boolean | boolean | false | Show a sweeping loading bar when the total is unknown. |
Auto-generated from Progress.props and inline _edit hints.
Segmented reference
Props
Control props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
segmentsts | array | Array< | [] | Ordered progress states. Each segment accepts key, label, value, a semantic tone, and an optional CSS colour override. |
value | number | number | 0 | Completed or otherwise processed item count used by the summary and accessibility value. |
max | number | number | — | Maximum item count. Defaults to the sum of all segments and expands when segment values grow. |
label | string | string | 'items processed' | Visible summary label and accessible progress label. |
tone | 'neutral' | 'primary' | 'success' | 'warning' | 'danger' | string | 'primary' | Semantic tone used by the percentage summary. |
size | 'sm' | 'md' | 'lg' | string | 'md' | Segmented track height. |
showSummary | boolean | boolean | true | Show the processed count, total, label, and optional percentage. |
showPercentage | boolean | boolean | true | Show the processed percentage in the summary. |
showLegend | boolean | boolean | true | Show segment counts and labels below the track. |
showZeroSegments | boolean | boolean | false | Keep zero-value states visible in the legend. |
Auto-generated from Segmented progress.props and inline _edit hints.
Radial reference
Props
Control props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
value | number | number | — | Current progress value. Leave empty for an empty meter. |
max | number | number | 100 | Maximum progress value. |
label | string | string | 'Progress' | Accessible progress label. |
tone | 'primary' | 'success' | 'warning' | 'danger' | 'neutral' | string | 'primary' | Progress stroke tone. |
size | 'sm' | 'md' | 'lg' | string | 'md' | Rendered diameter. Use a preset or a pixel number. |
showValue | boolean | boolean | true | Show the percentage inside the circle. |
Auto-generated from Radial progress.props and inline _edit hints.