HextaUI

Progress

Uma barra ou anel que mostra o quanto uma tarefa avançou, suaviza entre as atualizações e desliza enquanto o total é desconhecido.

Preparing…
x
"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  Progress,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"

export function ProgressDemo() {
  const [value, setValue] = React.useState<number | null>(null)
  const [run, setRun] = React.useState(0)

  React.useEffect(() => {
    let current = 0
    let tick: ReturnType<typeof setInterval> | undefined
    const start = setTimeout(() => {
      setValue(0)
      tick = setInterval(() => {
        current = Math.min(100, current + Math.round(Math.random() * 12 + 3))
        setValue(current)
        if (current === 100) {
          clearInterval(tick)
        }
      }, 400)
    }, 1200)
    return () => {
      clearTimeout(start)
      clearInterval(tick)
    }
  }, [run])

  const label =
    value === null ? "Preparing…" : value === 100 ? "Uploaded" : "Uploading"

  return (
    <div className="flex w-full max-w-sm flex-col items-center gap-6">
      <Progress value={value}>
        <ProgressLabel>{label}</ProgressLabel>
        <ProgressValue />
      </Progress>
      <Button
        variant="outline"
        size="sm"
        onClick={() => {
          setValue(null)
          setRun(run + 1)
        }}
      >
        Restart
      </Button>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/progress.json

Adiciona o componente, os tokens de tema do HextaUI e quaisquer componentes do HextaUI dos quais ele depende.

import {
  Progress,
  ProgressCircle,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"
<Progress value={40}>
  <ProgressLabel>Uploading</ProgressLabel>
  <ProgressValue />
</Progress>

<ProgressCircle value={40} aria-label="Uploading" />

<Progress /> desenha sua própria trilha e indicador depois dos filhos, então um rótulo e um valor ficam em uma linha acima da barra. Cada atualização suaviza o preenchimento a partir de onde está, então atualizações rápidas parecem um único movimento fluido em vez de degraus.

Progress
├── ProgressLabel
└── ProgressValue

ProgressCircle
└── ProgressValue

Tamanhos

xs, sm, default e lg mudam a espessura da barra. xs é a linha fina que o Attachment desenha na borda inferior.

import {
  Progress,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"

export function ProgressSizes() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-6">
      <Progress value={15} size="xs">
        <ProgressLabel>Extra small</ProgressLabel>
        <ProgressValue />
      </Progress>
      <Progress value={30} size="sm">
        <ProgressLabel>Small</ProgressLabel>
        <ProgressValue />
      </Progress>
      <Progress value={55}>
        <ProgressLabel>Default</ProgressLabel>
        <ProgressValue />
      </Progress>
      <Progress value={80} size="lg">
        <ProgressLabel>Large</ProgressLabel>
        <ProgressValue />
      </Progress>
    </div>
  )
}

Status

variant colore apenas o preenchimento ou o anel, então a trilha, o rótulo e o valor permanecem neutros.

import {
  Progress,
  ProgressCircle,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"

export function ProgressVariants() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-6">
      <Progress value={100} variant="success">
        <ProgressLabel>Backup complete</ProgressLabel>
        <ProgressValue />
      </Progress>
      <Progress value={86} variant="warning">
        <ProgressLabel>Storage almost full</ProgressLabel>
        <ProgressValue />
      </Progress>
      <Progress value={47} variant="destructive">
        <ProgressLabel>Upload failed</ProgressLabel>
        <ProgressValue />
      </Progress>
      <div className="flex items-center gap-4">
        <ProgressCircle value={100} variant="success" aria-label="Synced" />
        <ProgressCircle value={86} variant="warning" aria-label="Almost full" />
        <ProgressCircle value={47} variant="destructive" aria-label="Failed" />
      </div>
    </div>
  )
}

Indeterminado

Passe value={null} enquanto o total é desconhecido. Um segmento desliza pela trilha e, quando chega um número, o preenchimento cresce a partir do início.

import { Progress, ProgressLabel } from "@/components/ui/progress"

export function ProgressIndeterminate() {
  return (
    <div className="w-full max-w-sm">
      <Progress value={null}>
        <ProgressLabel>Connecting to server…</ProgressLabel>
      </Progress>
    </div>
  )
}

Círculo

<ProgressCircle /> desenha o mesmo valor como um anel, começando no topo. Os filhos ficam no meio, o que combina com <ProgressValue /> em lg e xl.

import { ProgressCircle, ProgressValue } from "@/components/ui/progress"

export function ProgressCircleDemo() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-8">
      <ProgressCircle value={25} size="sm" aria-label="Small" />
      <ProgressCircle value={50} aria-label="Default" />
      <ProgressCircle value={75} size="lg" aria-label="Large">
        <ProgressValue />
      </ProgressCircle>
      <ProgressCircle value={100} size="xl" aria-label="Extra large">
        <ProgressValue />
      </ProgressCircle>
    </div>
  )
}

Círculo indeterminado

Um arco gira em volta do anel até chegar um valor.

import { ProgressCircle } from "@/components/ui/progress"

export function ProgressCircleIndeterminate() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-8">
      <ProgressCircle value={null} size="sm" aria-label="Syncing" />
      <ProgressCircle value={null} aria-label="Syncing" />
      <ProgressCircle value={null} size="lg" aria-label="Syncing" />
      <ProgressCircle value={null} size="xl" aria-label="Syncing" />
    </div>
  )
}

Intervalo e formato personalizados

Defina min e max para qualquer intervalo, format para o número e um filho em função em <ProgressValue /> para o texto. Dê aos leitores de tela as mesmas palavras com getAriaValueText.

"use client"

import {
  Progress,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"

export function ProgressFormat() {
  return (
    <div className="w-full max-w-sm">
      <Progress
        value={37.5}
        max={50}
        format={{ maximumFractionDigits: 1 }}
        getAriaValueText={(formatted) => `${formatted} of 50 GB used`}
      >
        <ProgressLabel>Storage</ProgressLabel>
        <ProgressValue>{(formatted) => `${formatted} of 50 GB`}</ProgressValue>
      </Progress>
    </div>
  )
}

Valor animado

Renderize <NumberFlow /> dentro de <ProgressValue /> para que só os dígitos que mudam girem, em sincronia com o preenchimento.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { NumberFlow } from "@/components/ui/number-flow"
import {
  Progress,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"

export function ProgressNumberFlow() {
  const [value, setValue] = React.useState(42)

  return (
    <div className="flex w-full max-w-sm flex-col items-center gap-6">
      <Progress value={value}>
        <ProgressLabel>Course completed</ProgressLabel>
        <ProgressValue>
          {(_, current) => <NumberFlow value={current ?? 0} suffix="%" />}
        </ProgressValue>
      </Progress>
      <div className="flex gap-2">
        <Button
          variant="outline"
          size="sm"
          onClick={() => setValue(Math.max(0, value - 13))}
        >
          −13
        </Button>
        <Button
          variant="outline"
          size="sm"
          onClick={() => setValue(Math.min(100, value + 13))}
        >
          +13
        </Button>
      </div>
    </div>
  )
}

Rótulos longos

Nomes longos quebram em suas próprias linhas e o valor fica no final. Os anéis funcionam como status compacto ao lado de cada linha.

import {
  Progress,
  ProgressCircle,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"

const files = [
  {
    name: "quarterly-report-final-v3-approved-by-legal-and-finance.pdf",
    value: 64,
  },
  { name: "IMG_20260914_183022_HDR_edited_export.jpg", value: 100 },
  { name: "brand-assets.zip", value: 12 },
]

export function ProgressFiles() {
  return (
    <ul className="flex w-full max-w-sm flex-col gap-5">
      {files.map((file) => (
        <li key={file.name} className="flex items-start gap-3">
          <ProgressCircle value={file.value} aria-label={file.name} />
          <div className="min-w-0 flex-1">
            <Progress value={file.value} size="sm">
              <ProgressLabel>{file.name}</ProgressLabel>
              <ProgressValue />
            </Progress>
          </div>
        </li>
      ))}
    </ul>
  )
}

Sem rótulo visível

Dê nome à barra com aria-label quando o contexto já diz o que está carregando.

import { Progress } from "@/components/ui/progress"

export function ProgressUnlabeled() {
  return (
    <div className="w-full max-w-sm">
      <Progress value={45} aria-label="Profile setup" />
    </div>
  )
}

Da direita para a esquerda

O preenchimento e o deslizamento indeterminado começam pela direita. Passe locale para formatar o valor com os dígitos do leitor.

import {
  Progress,
  ProgressCircle,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"

export function ProgressRtl() {
  return (
    <div dir="rtl" className="flex w-full max-w-sm flex-col gap-6">
      <Progress value={65} locale="ar-EG">
        <ProgressLabel>جارٍ التحميل</ProgressLabel>
        <ProgressValue />
      </Progress>
      <Progress value={null}>
        <ProgressLabel>جارٍ الاتصال…</ProgressLabel>
      </Progress>
      <ProgressCircle value={65} size="xl" locale="ar-EG" aria-label="التقدم">
        <ProgressValue />
      </ProgressCircle>
    </div>
  )
}
  • A raiz é uma progressbar com aria-valuenow, aria-valuemin, aria-valuemax e um aria-valuetext formatado. Enquanto indeterminada, não tem valor atual.
  • <ProgressLabel /> dá nome à barra. Sem um, passe aria-label.
  • <ProgressValue /> fica oculto dos leitores de tela, já que a progressbar já anuncia o valor.
  • Com movimento reduzido, o preenchimento salta para cada novo valor, e a barra e o anel indeterminados pulsam no lugar em vez de se mover.
  • Os valores são formatados em en-US a menos que você passe locale, para o servidor e o navegador renderizarem o mesmo texto.

Construído sobre o progress do Base UI. Cada parte aceita as props da primitiva que envolve.

PropTipoPadrão
valuenull torna a barra indeterminada.
number | null–
min
number0
max
number100
size
"xs" | "sm" | "default" | "lg""default"
variant
"default" | "success" | "warning" | "destructive""default"
formatFormata o valor. Sem isso, o valor aparece como porcentagem.
Intl.NumberFormatOptions–
locale
Intl.LocalesArgument"en-US"
getAriaValueText
(formattedValue: string, value: number | null) => string–
className
string | (state) => string–
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescrição
data-slot="progress"A raiz.
data-sizeO tamanho: xs, sm, default ou lg.
data-variantA variante de status.
data-progressingPresente enquanto o valor está abaixo de max.
data-completePresente quando o valor alcança max.
data-indeterminatePresente quando o valor é null ou não é um número finito.

Dá nome à progressbar. Renderiza um <span> e recebe os mesmos atributos de estado que a raiz.

PropTipoPadrão
render
ReactElement | (props, state) => ReactElement<span>
AtributoDescrição
data-slot="progress-label"O rótulo.
PropTipoPadrão
childrenTexto personalizado. Sem ele, mostra o valor formatado, ou nada enquanto indeterminado.
(formattedValue: string | null, value: number | null) => ReactNode–
render
ReactElement | (props, state) => ReactElement<span>
AtributoDescrição
data-slot="progress-value"O valor.

Renderizado por <Progress /> e dimensionado por seu size. Exportado para composições personalizadas.

AtributoDescrição
data-slot="progress-track"A trilha.
--progress-dir1, ou -1 da direita para a esquerda, para o deslizamento indeterminado seguir a direção de leitura.

O preenchimento. Sua largura é definida inline a partir do valor e suaviza entre atualizações.

AtributoDescrição
data-slot="progress-indicator"O preenchimento.
PropTipoPadrão
valuenull gira um arco.
number | null–
min
number0
max
number100
size
"sm" | "default" | "lg" | "xl""default"
variant
"default" | "success" | "warning" | "destructive""default"
locale
Intl.LocalesArgument"en-US"
childrenExibido no meio do anel.
ReactNode–
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescrição
data-slot="progress-circle"A raiz.
data-sizeO tamanho: sm, default, lg ou xl.
data-variantA variante de status.
data-progressingPresente enquanto o valor está abaixo de max.
data-completePresente quando o valor alcança max.
data-indeterminatePresente quando o valor é null ou não é um número finito.
--progress-circle-sizeA largura e a altura do anel.
--progress-strokeA largura do traço do anel.