Progress
Pokazuje, jak daleko zaszło zadanie — albo że trwa, gdy nie wiadomo, ile jeszcze potrwa.
import {
Progress,
ProgressLabel,
ProgressValue,
} from "@/components/ui/progress"
export default function ProgressDemo() {
return (
<Progress value={64} className="w-full max-w-xs">
<ProgressLabel>Przesyłanie zasobów</ProgressLabel>
<ProgressValue />
</Progress>
)
}Anatomia
- 1EtykietaMówi, co jest w toku. Nazywa też pasek dla czytników ekranu.
- 2TorCałe zadanie, w stonowanym odcieniu.
- 3WartośćProcent zapisany cyframi tabelarycznymi, dzięki czemu nie drga przy zmianie.
- 4WskaźnikUkończona część. Animuje przejścia między wartościami, a gdy wartość jest nieznana, przesuwa się wzdłuż toru.
Instalacja
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/progress.jsonUżycie
import { Progress, ProgressLabel, ProgressValue } from "@/components/ui/progress"<Progress value={64}>
<ProgressLabel>Uploading assets</ProgressLabel>
<ProgressValue />
</Progress>Progress sam renderuje tor i wskaźnik. Elementy potomne — zwykle etykieta i wartość — trafiają do wiersza nad paskiem.
Przykłady
Rozmiary
sm (4 px) mieści się w listach i komórkach tabel, default (6 px) pasuje do większości formularzy i kart, a lg (10 px) jest przeznaczony dla paska postępu, który jest głównym elementem widoku.
import {
Progress,
ProgressLabel,
ProgressValue,
} from "@/components/ui/progress"
export default function ProgressSizes() {
return (
<div className="flex w-full max-w-sm flex-col gap-6">
<Progress value={30} size="sm">
<ProgressLabel>Mały</ProgressLabel>
<ProgressValue />
</Progress>
<Progress value={55}>
<ProgressLabel>Domyślny</ProgressLabel>
<ProgressValue />
</Progress>
<Progress value={80} size="lg">
<ProgressLabel>Duży</ProgressLabel>
<ProgressValue />
</Progress>
</div>
)
}Stan nieokreślony
Przekaż value={null}, gdy wiesz, że praca trwa, ale nie wiesz, ile jej zostało. Fragment paska przesuwa się wzdłuż toru, dopóki nie przełączysz go na określoną wartość.
import { Progress, ProgressLabel } from "@/components/ui/progress"
export default function ProgressIndeterminate() {
return (
<Progress value={null} className="w-full max-w-xs">
<ProgressLabel>Przygotowujemy eksport…</ProgressLabel>
</Progress>
)
}Aktualizacje na żywo
Wskaźnik animuje przejścia między wartościami z czasem trwania slow i krzywą standard, więc częste aktualizacje wyglądają jak płynny ruch, a nie skoki.
"use client"
import * as React from "react"
import { RotateCcwIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Progress,
ProgressLabel,
ProgressValue,
} from "@/components/ui/progress"
export default function ProgressLive() {
const [value, setValue] = React.useState(0)
const [run, setRun] = React.useState(0)
React.useEffect(() => {
// Each run counts up from zero; Restart resets the value and starts a new run.
let progress = 0
const timer = setInterval(() => {
progress = Math.min(100, progress + Math.round(Math.random() * 12 + 4))
setValue(progress)
if (progress === 100) clearInterval(timer)
}, 450)
return () => clearInterval(timer)
}, [run])
return (
<div className="flex w-full max-w-xs flex-col items-end gap-4">
<Progress value={value} className="w-full">
<ProgressLabel>
{value === 100 ? "Budowanie zakończone" : "Budowanie…"}
</ProgressLabel>
<ProgressValue />
</Progress>
<Button
variant="outline"
size="xs"
onClick={() => {
setValue(0)
setRun((n) => n + 1)
}}
>
<RotateCcwIcon data-icon="inline-start" />
Uruchom ponownie
</Button>
</div>
)
}Przesyłanie plików
W listach użyj rozmiaru sm, a etykietą niech będzie nazwa pliku. Gdy pozycja się zakończy, zastąp wartość potwierdzeniem, aby ukończone wiersze przestały przyciągać wzrok.
- brand-guidelines.pdfGotowe4,2 MB
- hero-illustration.png2,8 MB
- product-walkthrough.mp4148 MB
import {
CircleCheckIcon,
FileTextIcon,
ImageIcon,
FileVideoIcon,
} from "lucide-react"
import {
Progress,
ProgressLabel,
ProgressValue,
} from "@/components/ui/progress"
const files = [
{
name: "brand-guidelines.pdf",
size: "4,2 MB",
value: 100,
icon: FileTextIcon,
},
{ name: "hero-illustration.png", size: "2,8 MB", value: 72, icon: ImageIcon },
{
name: "product-walkthrough.mp4",
size: "148 MB",
value: 18,
icon: FileVideoIcon,
},
]
export default function ProgressFileUpload() {
return (
<ul className="flex w-full max-w-md flex-col divide-y rounded-xl border bg-card shadow-raised">
{files.map((file) => {
const Icon = file.icon
const done = file.value === 100
return (
<li key={file.name} className="flex items-start gap-3 p-4">
<span className="flex size-9 shrink-0 items-center justify-center rounded-lg border bg-surface text-muted-foreground">
<Icon className="size-4" />
</span>
<Progress value={file.value} size="sm" className="min-w-0 flex-1">
<ProgressLabel className="truncate">{file.name}</ProgressLabel>
{done ? (
<span className="ml-auto flex items-center gap-1 text-sm text-success-11">
<CircleCheckIcon className="size-4" />
Gotowe
</span>
) : (
<ProgressValue />
)}
<span className="order-last w-full text-xs text-muted-foreground">
{file.size}
</span>
</Progress>
</li>
)
})}
</ul>
)
}Własna wartość
ProgressValue przyjmuje funkcję renderującą dla jednostek innych niż procent. Gdy zmieniasz widoczny format, przekaż technologiom wspomagającym te same słowa przez getAriaValueText.
"use client"
import {
Progress,
ProgressLabel,
ProgressValue,
} from "@/components/ui/progress"
const used = 7.2
const total = 10
const format = (value: number) => value.toLocaleString("pl-PL")
export default function ProgressCustomValue() {
return (
<Progress
value={used}
max={total}
getAriaValueText={() =>
`Wykorzystano ${format(used)} z ${format(total)} gigabajtów`
}
className="w-full max-w-xs"
>
<ProgressLabel>Miejsce na dane</ProgressLabel>
<ProgressValue>
{() => `${format(used)} GB z ${format(total)} GB`}
</ProgressValue>
</Progress>
)
}<Progress
value={7.2}
max={10}
getAriaValueText={() => "7.2 of 10 gigabytes used"}
>
<ProgressLabel>Storage</ProgressLabel>
<ProgressValue>{() => "7.2 GB of 10 GB"}</ProgressValue>
</Progress>Wytyczne
Kiedy używać
- Do zadań, które trwają dłużej niż około sekundy i raportują mierzalny postęp: przesyłania, eksportu, importu, budowania.
- Do wielkości mierzonych względem limitu — miejsca na dane, licencji, przydziałów — gdy pasek jest wskaźnikiem poziomu, a nie zegarem.
Kiedy nie używać
- Do krótkich lub niemierzalnych oczekiwań wewnątrz kontrolki — użyj Spinner albo stanu
loadingkomponentu Button. - Do treści, która wciąż się ładuje — użyj Skeleton, który zapowiada układ.
- Do kroków w procesie. Pasek postępu mierzy wykonaną pracę, a nie nawigację.
Skeleton, Spinner czy Progress?
Bądź uczciwy
Nigdy nie pozwól, by określony pasek utknął na 99% albo się cofnął. Jeśli nie potrafisz wiarygodnie oszacować postępu, użyj stanu nieokreślonego i opisz w etykiecie, co się dzieje.
Dostępność
- Renderuje
role="progressbar"zaria-valuemin,aria-valuemaxiaria-valuenow. Stan nieokreślony pomijaaria-valuenow— czytniki ekranu odczytują wtedy pasek jako zajęty. ProgressLabeljest powiązany z paskiem przezaria-labelledby. Gdy nie możesz pokazać etykiety, przekaż zamiast niejaria-labeldoProgress— pasek postępu zawsze musi mieć nazwę.aria-valuetextjest generowany ze sformatowanej wartości. Nadpisz go przezgetAriaValueTextza każdym razem, gdy widoczna wartość nie jest procentem.- Paski postępu nie są regionami na żywo (live regions): czytniki ekranu odczytują wartość, gdy użytkownik do niej przejdzie, a nie przy każdej zmianie. Kamienie milowe — Przesyłanie zakończone — ogłaszaj przez Toast lub komunikat z
role="status". - Przy
prefers-reduced-motionzmiany wartości i przesuwanie się paska w stanie nieokreślonym nie są animowane.
Dokumentacja API
Progress
Komponent główny. Renderuje <div> z twoimi elementami potomnymi, po których następują ProgressTrack i ProgressIndicator. Przyjmuje wszystkie propsy Progress.Root z Base UI.
ProgressLabel
Dostępna nazwa paska. Renderuje <span>.
ProgressValue
Wyświetla sformatowaną wartość wyrównaną do prawej. Renderuje <span>.
ProgressTrack i ProgressIndicator
Progress renderuje je automatycznie. Są eksportowane na potrzeby własnych kompozycji — na przykład segmentowego miernika, który renderuje kilka wskaźników.