Une barre qui se remplit à mesure que quelque chose avance, ou s'anime en continu quand il n'y a aucun chiffre à rapporter. Elle peut être dressée à la verticale, et porter son propre pourcentage à l'intérieur.
value est l'avancement, face à un max qui dit ce qui compte comme terminé. Tout ce qui sort de la plage y est ramené.
vue
40 %40 %
value = 40, max = 100
7 of 12 files, max = 12
58 %58 %
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VProgressLinear, VTypography } from 'vectis-ui'
const value = ref(40)
const step = (delta: number) => (value.value += delta)
</script>
<template>
<div class="column">
<VProgressLinear :value="value" thickness="10" label="Upload" show-value />
<div class="row">
<VButton variant="outline" tone="neutral" size="sm" @click="step(-25)">-25</VButton>
<VButton variant="outline" tone="neutral" size="sm" @click="step(25)">+25</VButton>
<!-- Anything outside the range is brought back into it, so a value of 130 or -10
needs no clamping of your own. -->
<VTypography variant="body-sm" tone="muted">value = {{ value }}, max = 100</VTypography>
</div>
<!-- `max` says what counts as finished. The other end is always zero. -->
<div class="labelled">
<VTypography variant="caption" tone="muted">7 of 12 files, max = 12</VTypography>
<VProgressLinear :value="7" :max="12" thickness="10" label="Files" show-value />
</div>
</div>
</template>
<style scoped>
.column {
display: grid;
gap: var(--vectis-space-5);
max-inline-size: 28rem;
}
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--vectis-space-3);
}
.labelled {
display: grid;
gap: var(--vectis-space-2);
}
</style>
Indéterminé
indeterminate sert à une attente qui ne se mesure pas : la barre s'anime en continu et la valeur est ignorée. Sous animation réduite, elle est ralentie plutôt qu'arrêtée.
vue
For a server that reports no percentage. Under reduced motion the bar is slowed rather than stopped: a motionless loader no longer says that anything is happening, which is the one thing it exists to say.
<script setup lang="ts">
import { VProgressLinear, VTypography } from 'vectis-ui'
</script>
<template>
<div class="column">
<!-- One bar crosses the track, flush with each edge at the extremes, so the loop is
invisible and the track is never empty. The value is ignored: there is nothing
to report. -->
<VProgressLinear indeterminate label="Waiting for the server" />
<VProgressLinear indeterminate tone="neutral" thickness="8" label="Loading" />
<VTypography variant="body-sm" tone="muted">
For a server that reports no percentage. Under reduced motion the bar is slowed rather than
stopped: a motionless loader no longer says that anything is happening, which is the one thing
it exists to say.
</VTypography>
</div>
</template>
<style scoped>
.column {
display: grid;
gap: var(--vectis-space-5);
max-inline-size: 28rem;
}
</style>
Tonalités
tone dit ce que signifie la progression, sous forme de couleur. Il y en a cinq plutôt que les trois d'un bouton, une barre rendant compte d'un état plutôt que d'amorcer une action.
color remplace le ton, en hexadécimal, en nom CSS ou en oklch(). La nuance du rail en est dérivée face au thème.
vue
A hex value
A CSS colour name
An oklch() colour
<script setup lang="ts">
import { VProgressLinear, VTypography } from 'vectis-ui'
const COLORS = [
{ value: '#7c3aed', caption: 'A hex value' },
{ value: 'teal', caption: 'A CSS colour name' },
{ value: 'oklch(0.72 0.19 45)', caption: 'An oklch() colour' },
]
</script>
<template>
<div class="column">
<div v-for="colour in COLORS" :key="colour.value" class="row">
<VTypography variant="caption" tone="muted" class="name">{{ colour.caption }}</VTypography>
<!-- The track's own shade is derived from the colour against the theme, so both
follow: one value to set, and it stays right in dark mode. -->
<VProgressLinear :value="60" :color="colour.value" :label="`Upload, ${colour.caption}`" />
</div>
</div>
</template>
<style scoped>
.column {
display: grid;
gap: var(--vectis-space-4);
max-inline-size: 28rem;
}
.row {
display: grid;
grid-template-columns: 9rem 1fr;
align-items: center;
gap: var(--vectis-space-4);
}
.name {
text-align: end;
}
</style>
Épaisseur
thickness est toujours en pixels, qu'elle soit donnée en nombre ou en chaîne numérique, et vaut 4px sauf indication contraire. Il n'y a pas de prop de longueur à côté : la barre prend la largeur de ce qui la contient.
vue
2px
4px
8px
16px
<script setup lang="ts">
import { VProgressLinear, VTypography } from 'vectis-ui'
const THICKNESSES = [2, 4, 8, 16]
</script>
<template>
<div class="column">
<!-- Always pixels, whether a number or a numeric string. The bar takes the width of
whatever holds it, so its length is the container's business and not a prop. -->
<div v-for="thickness in THICKNESSES" :key="thickness" class="row">
<VTypography variant="caption" tone="muted" class="name">{{ thickness }}px</VTypography>
<VProgressLinear :value="60" :thickness="thickness" :label="`Upload, ${thickness}px`" />
</div>
</div>
</template>
<style scoped>
.column {
display: grid;
gap: var(--vectis-space-4);
max-inline-size: 28rem;
}
.row {
display: grid;
grid-template-columns: 4rem 1fr;
align-items: center;
gap: var(--vectis-space-4);
}
.name {
text-align: end;
}
</style>
Forme
shape dit si les extrémités de la barre sont arrondies ou coupées net. Cela se voit sur une barre épaisse et presque pas sur les 4px par défaut.
showValue écrit le pourcentage dans la barre et valuePosition dit où ce texte se place le long de celle-ci. Le slot par défaut remplace le chiffre et reçoit la valeur, le max et le pourcentage. Le contenu est rendu deux fois, une fois sur le rail et une fois sur le remplissage : il doit donc être sans effet de bord.
vue
valuePosition start
55 %55 %
valuePosition center
55 %55 %
valuePosition end
55 %55 %
A slot of your own
7 of 12 files7 of 12 files
The bar is 4px by default, so writing inside it means giving it a thickness that can hold a line of text.
<script setup lang="ts">
import { VProgressLinear, VTypography, type ProgressLinearValuePosition } from 'vectis-ui'
const POSITIONS: ProgressLinearValuePosition[] = ['start', 'center', 'end']
</script>
<template>
<div class="column">
<!-- The text is rendered TWICE: once over the empty track, once over the fill in a
contrasting colour, the second copy clipped at the fill's edge. Whatever the
slot renders must therefore be free of side effects. -->
<div v-for="position in POSITIONS" :key="position" class="labelled">
<VTypography variant="caption" tone="muted">valuePosition {{ position }}</VTypography>
<VProgressLinear
:value="55"
thickness="22"
show-value
:value-position="position"
:label="`Upload, ${position}`"
/>
</div>
<!-- The slot replaces the percentage and receives the value, the max and the
percentage worked out from them. -->
<div class="labelled">
<VTypography variant="caption" tone="muted">A slot of your own</VTypography>
<VProgressLinear :value="7" :max="12" thickness="22" label="Files uploaded">
<template #default="{ value, max }">{{ value }} of {{ max }} files</template>
</VProgressLinear>
</div>
<VTypography variant="body-sm" tone="muted">
The bar is 4px by default, so writing inside it means giving it a thickness that can hold a
line of text.
</VTypography>
</div>
</template>
<style scoped>
.column {
display: grid;
gap: var(--vectis-space-5);
max-inline-size: 28rem;
}
.labelled {
display: grid;
gap: var(--vectis-space-2);
}
</style>
Orientation
orientation à vertical remplit la barre du bas vers le haut, y compris dans une page de droite à gauche. Sa longueur est un token, 10rem par défaut, que votre propre hauteur remplace. Les copies de texte restent horizontales.
vue
25%
60%
90%
indeterminate
<script setup lang="ts">
import { VProgressLinear, VTypography } from 'vectis-ui'
</script>
<template>
<div class="row">
<!-- Upright, the bar fills from the BOTTOM up: zero is anchored to the bottom edge,
so a vertical bar reads the way a gauge does. It takes the height of whatever
holds it, the way the horizontal one takes the width. -->
<div v-for="value in [25, 60, 90]" :key="value" class="gauge">
<VProgressLinear
:value="value"
orientation="vertical"
thickness="14"
:label="`Tank, ${value} percent`"
/>
<VTypography variant="caption" tone="muted">{{ value }}%</VTypography>
</div>
<div class="gauge">
<VProgressLinear indeterminate orientation="vertical" thickness="14" label="Filling" />
<VTypography variant="caption" tone="muted">indeterminate</VTypography>
</div>
</div>
</template>
<style scoped>
.row {
display: flex;
align-items: end;
gap: var(--vectis-space-6);
}
.gauge {
display: grid;
justify-items: center;
gap: var(--vectis-space-2);
/* The bar takes the height it is given, exactly as the horizontal one takes a width. */
block-size: 9rem;
grid-template-rows: 1fr auto;
}
</style>
API
Props
Prop
Type
Défaut
value
number
0
Où en est la progression. Tout ce qui sort de l'intervalle y est ramené.
label
string
aucune
Ce qui progresse, en mots, pour les lecteurs d'écran. Rien ne s'affiche à l'écran, et le dictionnaire du design system sert de repli ; un aria-label ou un aria-labelledby à vous l'emporte sur lui.
max
number
100
Ce qui compte comme terminé. L'autre extrémité est toujours zéro.
indeterminate
boolean
false
Dit que la progression ne peut pas être mesurée : la barre s'anime en continu et la valeur est ignorée. C'est ce qu'il faut utiliser en attendant un serveur qui ne rapporte aucun pourcentage.
Ce que la progression signifie, exprimé en couleur.
color
string
aucune
Une couleur à vous, en hexadécimal, en nom CSS ou en oklch(), qui remplace le ton. La nuance de la piste en est dérivée face au thème, elle suit donc les deux.
thickness
number | string
aucune
L'épaisseur de la barre, toujours EN PIXELS : 12 et '12' donnent tous deux 12px. Elle vaut 4px par défaut : afficher du texte dans la barre demande donc une épaisseur explicite.
shape
ProgressLinearShape'rounded' | 'square'
'rounded'
Si les extrémités de la barre sont arrondies ou carrées.
showValue
boolean
false
Écrit le pourcentage dans la barre. C'est ignoré tant que la progression n'est pas mesurable, faute de chiffre à écrire.
Ce qu'il faut écrire dans la barre à la place du pourcentage. Il est rendu DEUX FOIS, une fois sur la piste vide et une fois sur la partie remplie dans une couleur contrastée, chaque copie étant coupée au bord du remplissage : ce qu'il rend doit donc être exempt d'effets de bord.
Types
Les types que les tables ci-dessus nomment, écrits comme la librairie les déclare. Ceux qui portent export s'importent depuis vectis-ui pour typer votre propre code ; les autres décrivent la forme de ce qu'un slot fournit.
export interface ProgressLinearSlotProps {
value: number
max: number
percent: number
}