HextaUI

Progress

タスクの進み具合を示すバーまたはリングです。更新のたびに滑らかに動き、全体量が不明なときはスライドします。

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

コンポーネント、HextaUIのテーマトークン、依存するHextaUIコンポーネントを追加します。

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

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

<Progress /> は子要素の後ろに独自のトラックとインジケーターを描画するため、ラベルと値はバーの上の1行に収まります。更新のたびに塗りは現在位置からなめらかに動くので、素早い更新も段階的ではなく1つの滑らかな動きに見えます。

Progress
├── ProgressLabel
└── ProgressValue

ProgressCircle
└── ProgressValue

サイズ

xs、sm、default、lg はバーの太さを変えます。xs は Attachment が下端に引く細い線です。

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>
  )
}

ステータス

variant は塗りまたはリングだけに色を付けるため、トラック、ラベル、値はニュートラルのままです。

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>
  )
}

不確定

合計が不明な間は value={null} を渡します。トラック上を区間がスライドし、数値が届くと塗りが先頭から伸びます。

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>
  )
}

円形

<ProgressCircle /> は同じ値を、上から始まるリングとして描画します。子要素は中央に置かれ、lg と xl の <ProgressValue /> に合います。

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>
  )
}

不確定な円形

値が届くまで、円弧がリングの周りを回ります。

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>
  )
}

カスタムの範囲とフォーマット

任意の範囲には min と max、数値には format、テキストには <ProgressValue /> の関数の子要素を設定します。同じ言葉をスクリーンリーダーにも伝えるには 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>
  )
}

アニメーションする値

<ProgressValue /> の内側に <NumberFlow /> を描画すると、変化した桁だけが塗りに合わせて回転します。

"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>
  )
}

長いラベル

長い名前は別の行に折り返され、値は末尾に残ります。リングは各行の横にコンパクトなステータスとして使えます。

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>
  )
}

表示されるラベルなし

何を読み込んでいるかが文脈からすでにわかる場合は、aria-label でバーに名前を付けます。

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>
  )
}

右から左

塗りと不確定のスライドは右から始まります。読み手の数字で値をフォーマットするには locale を渡します。

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>
  )
}
  • ルートは progressbar で、aria-valuenow、aria-valuemin、aria-valuemax、フォーマット済みの aria-valuetext を持ちます。不確定の間は現在の値を持ちません。
  • <ProgressLabel /> はバーに名前を付けます。ない場合は aria-label を渡してください。
  • progressbar がすでに値を読み上げるため、<ProgressValue /> はスクリーンリーダーから隠されます。
  • 視差効果の軽減が有効な場合、塗りは新しい値へ直接移り、不確定のバーとリングは動く代わりにその場で脈動します。
  • locale を渡さない限り値は en-US でフォーマットされるため、サーバーとブラウザーで同じテキストが描画されます。

Base UI の progress 上に構築されています。すべてのパーツは、ラップしているプリミティブの props を受け付けます。

プロパティ型デフォルト
valuenull にするとバーが不確定になります。
number | null–
min
number0
max
number100
size
"xs" | "sm" | "default" | "lg""default"
variant
"default" | "success" | "warning" | "destructive""default"
format値をフォーマットします。指定しない場合、値はパーセントで表示されます。
Intl.NumberFormatOptions–
locale
Intl.LocalesArgument"en-US"
getAriaValueText
(formattedValue: string, value: number | null) => string–
className
string | (state) => string–
render
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="progress"ルート。
data-sizeサイズ: xs、sm、default、lg。
data-variantステータスのバリアント。
data-progressing値が max 未満の間付きます。
data-complete値が max に達したときに付きます。
data-indeterminate値が null または有限の数値でないときに付きます。

progressbar に名前を付けます。<span> を描画し、ルートと同じ状態属性を受け付けます。

プロパティ型デフォルト
render
ReactElement | (props, state) => ReactElement<span>
属性説明
data-slot="progress-label"ラベル。
プロパティ型デフォルト
childrenカスタムテキスト。指定しない場合はフォーマット済みの値が表示され、不確定の間は何も表示されません。
(formattedValue: string | null, value: number | null) => ReactNode–
render
ReactElement | (props, state) => ReactElement<span>
属性説明
data-slot="progress-value"値。

<Progress /> によって描画され、その size でサイズが決まります。カスタム構成向けにエクスポートされています。

属性説明
data-slot="progress-track"トラック。
--progress-dir1、右から左では -1。不確定状態のスライドは文字の読み方向に従います。

塗り。幅は値からインラインで設定され、更新の間でなめらかに変化します。

属性説明
data-slot="progress-indicator"塗り。
プロパティ型デフォルト
valuenull で円弧が回ります。
number | null–
min
number0
max
number100
size
"sm" | "default" | "lg" | "xl""default"
variant
"default" | "success" | "warning" | "destructive""default"
locale
Intl.LocalesArgument"en-US"
childrenリングの中央に表示されます。
ReactNode–
render
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="progress-circle"ルート。
data-sizeサイズ: sm、default、lg、xl。
data-variantステータスのバリアント。
data-progressing値が max 未満の間付きます。
data-complete値が max に達したときに付きます。
data-indeterminate値が null または有限の数値でないときに付きます。
--progress-circle-sizeリングの幅と高さ。
--progress-strokeリングの線の太さ。