Components
Progress

Progress

An element that shows either determinate or indeterminate progress.

Usage

0 percent
import { useEffect, useState } from 'react'
import { Progress, type ProgressProps } from '~/components/ui/progress'

export const Demo = (props: ProgressProps) => {
  const [value, setValue] = useState(0)
  useEffect(() => {
    const interval = setInterval(() => {
      setValue((value) => (value === 100 ? 0 : value + 1))
    }, Math.random() * 500)

    return () => clearInterval(interval)
  })

  return <Progress {...props} value={value} min={0} max={100} />
}

Installation

1

Add Component

Insert code snippet into your project. Update import paths as needed.

import { Progress as ArkProgress, type ProgressRootProps } from '@ark-ui/react/progress'
import { forwardRef, type ReactNode } from 'react'
import { css, cx } from 'styled-system/css'
import { splitCssProps } from 'styled-system/jsx'
import { progress, type ProgressVariantProps } from 'styled-system/recipes'
import type { Assign, JsxStyleProps } from 'styled-system/types'

export interface ProgressProps
  extends Assign<JsxStyleProps, ProgressRootProps>,
    ProgressVariantProps {
  children?: ReactNode
  /**
   * The type of progress to render.
   * @default linear
   */
  type?: 'linear' | 'circular'
}

export const Progress = forwardRef<HTMLDivElement, ProgressProps>((props, ref) => {
  const [variantProps, progressProps] = progress.splitVariantProps(props)
  const [cssProps, localProps] = splitCssProps(progressProps)
  const { children, className, type = 'linear', ...rootProps } = localProps
  const styles = progress(variantProps)

  return (
    <ArkProgress.Root
      ref={ref}
      className={cx(styles.root, css(cssProps), className)}
      {...rootProps}
    >
      {children && <ArkProgress.Label className={styles.label}>{children}</ArkProgress.Label>}
      {type === 'linear' && (
        <ArkProgress.Track className={styles.track}>
          <ArkProgress.Range className={styles.range} />
        </ArkProgress.Track>
      )}
      {type === 'circular' && (
        <ArkProgress.Circle className={styles.circle}>
          <ArkProgress.CircleTrack className={styles.circleTrack} />
          <ArkProgress.CircleRange className={styles.circleRange} />
          <ArkProgress.ValueText className={styles.valueText} />
        </ArkProgress.Circle>
      )}
      <ArkProgress.ValueText className={styles.valueText} />
    </ArkProgress.Root>
  )
})

Progress.displayName = 'Progress'
2

Add Recipe

This step is necessary only if you do not use any of the Park UI plugins.

import { progressAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'

export const progress = defineSlotRecipe({
  className: 'progress',
  slots: progressAnatomy.keys(),
  base: {
    root: {
      alignItems: 'center',
      colorPalette: 'accent',
      display: 'flex',
      flexDirection: 'column',
      gap: '1.5',
      width: 'full',
    },
    label: {
      color: 'fg.default',
      fontWeight: 'medium',
      textStyle: 'sm',
    },
    track: {
      backgroundColor: 'bg.emphasized',
      borderRadius: 'l2',
      overflow: 'hidden',
      width: '100%',
    },
    range: {
      backgroundColor: 'colorPalette.default',
      height: '100%',
      transition: 'width 0.2s ease-in-out',
      '--translate-x': '-100%',
    },
    circleTrack: {
      stroke: 'bg.emphasized',
    },
    circleRange: {
      stroke: 'colorPalette.default',
      transitionProperty: 'stroke-dasharray, stroke',
      transitionDuration: '0.6s',
    },
    valueText: {
      textStyle: 'sm',
    },
  },
  defaultVariants: {
    size: 'md',
  },
  variants: {
    size: {
      sm: {
        circle: {
          '--size': '36px',
          '--thickness': '4px',
        },
        track: {
          height: '1.5',
        },
      },
      md: {
        track: {
          height: '2',
        },
        circle: {
          '--size': '40px',
          '--thickness': '4px',
        },
      },
      lg: {
        track: {
          height: '2.5',
        },
        circle: {
          '--size': '44px',
          '--thickness': '4px',
        },
      },
    },
  },
})

On this page