Skip to main content
ProgressCircle
@coinbase/cds-web@8.13.6
A circular visual indicator of completion progress.
Import
import { ProgressCircle } from '@coinbase/cds-web/visualizations/ProgressCircle'
SourceView source code
Peer dependencies
  • framer-motion: ^10.18.0

Default

Loading...
Live Code
<HStack gap={2}>
  <ProgressCircle progress={0} size={100} />
  <ProgressCircle progress={0.5} size={100} />
  <ProgressCircle progress={1} size={100} />
</HStack>

Thin

Loading...
Live Code
<HStack gap={2}>
  <ProgressCircle progress={0} weight="thin" size={100} />
  <ProgressCircle progress={0.5} weight="thin" size={100} />
  <ProgressCircle progress={1} weight="thin" size={100} />
</HStack>

Semiheavy

Loading...
Live Code
<HStack gap={2}>
  <ProgressCircle progress={0} weight="semiheavy" size={100} />
  <ProgressCircle progress={0.5} weight="semiheavy" size={100} />
  <ProgressCircle progress={1} weight="semiheavy" size={100} />
</HStack>

Heavy

Loading...
Live Code
<HStack gap={2}>
  <ProgressCircle progress={0} weight="heavy" size={100} />
  <ProgressCircle progress={0.5} weight="heavy" size={100} />
  <ProgressCircle progress={1} weight="heavy" size={100} />
</HStack>

No Text

Loading...
Live Code
<HStack gap={2}>
  <ProgressCircle progress={0} hideContent size={25} />
  <ProgressCircle progress={0.5} hideContent size={25} />
  <ProgressCircle progress={1} hideContent size={25} />
</HStack>

Disabled

Loading...
Live Code
<HStack gap={2}>
  <ProgressCircle progress={0} disabled size={100} />
  <ProgressCircle progress={0.5} disabled size={100} />
  <ProgressCircle progress={1} disabled size={100} />
</HStack>

Colors

Loading...
Live Code
<HStack gap={2}>
  <ProgressCircle progress={0.5} color="bgPositive" size={100} />
  <ProgressCircle progress={0.5} color="bgNegative" size={100} />
  <ProgressCircle progress={0.5} color="bgPrimary" size={100} />
  <ProgressCircle progress={0.5} color="fg" size={100} />
</HStack>

Fill Parent

The progress circle can be dynamically sized to fit its parent. If you drag the browser window smaller or larger then the ProgressCircle will resize accordingly.

Loading...
Live Code
<HStack gap={2} flexWrap="wrap">
  <div style={{ height: '15vw', width: '15vw', minWidth: '60px', minHeight: '60px' }}>
    <ProgressCircle progress={1} />
  </div>
  <div style={{ height: '10vw', width: '10vw', minWidth: '60px', minHeight: '60px' }}>
    <ProgressCircle progress={1} />
  </div>
  <div style={{ height: '5vw', width: '5vw', minWidth: '60px', minHeight: '60px' }}>
    <ProgressCircle progress={1} />
  </div>
</HStack>

Content Node Customization

You can override the default content node to display a custom node. Note that the content node is clipped to the circle.

With Asset

You can provide an image, such as an asset, as the content node.

Loading...
Live Code
<VStack gap={2}>
  <HStack gap={2}>
    <ProgressCircle
      progress={1}
      size={56}
      styles={{
        progress: {
          stroke: assets.eth.color,
        },
      }}
      contentNode={
        <Box height="100%" padding={0.25} width="100%">
          <RemoteImage
            alt={assets.eth.name}
            shape="circle"
            source={assets.eth.imageUrl}
            style={{ width: '100%', height: '100%' }}
          />
        </Box>
      }
      weight="thin"
    />

    <ProgressCircle
      progress={0.75}
      size={56}
      styles={{
        progress: {
          stroke: assets.ltc.color,
        },
      }}
      contentNode={
        <Box height="100%" padding={0.25} width="100%">
          <RemoteImage
            alt={assets.ltc.name}
            shape="circle"
            source={assets.ltc.imageUrl}
            style={{ width: '100%', height: '100%' }}
          />
        </Box>
      }
      weight="thin"
    />
    <ProgressCircle
      progress={0.5}
      size={56}
      styles={{
        progress: {
          stroke: assets.dai.color,
        },
      }}
      contentNode={
        <Box height="100%" padding={0.25} width="100%">
          <RemoteImage
            shape="circle"
            source={assets.dai.imageUrl}
            style={{ width: '100%', height: '100%' }}
          />
        </Box>
      }
      weight="thin"
    />
    <ProgressCircle
      progress={0.25}
      size={56}
      styles={{
        progress: {
          stroke: assets.sushi.color,
        },
      }}
      contentNode={
        <Box height="100%" padding={0.25} width="100%">
          <RemoteImage
            alt={assets.sushi.name}
            shape="circle"
            source={assets.sushi.imageUrl}
            style={{ width: '100%', height: '100%' }}
          />
        </Box>
      }
      weight="thin"
    />
    <ProgressCircle
      progress={0}
      size={56}
      styles={{
        progress: {
          stroke: assets.xrp.color,
        },
      }}
      contentNode={
        <Box height="100%" padding={0.25} width="100%">
          <RemoteImage
            alt={assets.xrp.name}
            shape="circle"
            source={assets.xrp.imageUrl}
            style={{ width: '100%', height: '100%' }}
          />
        </Box>
      }
      weight="thin"
    />
  </HStack>
  <HStack gap={2}>
    <ProgressCircle
      styles={{
        progress: {
          stroke: assets.btc.color,
        },
      }}
      progress={0.24}
      size={24}
      contentNode={
        <Box height="100%" padding={0.25} width="100%">
          <RemoteImage
            alt={assets.btc.name}
            shape="circle"
            source={assets.btc.imageUrl}
            style={{ width: '100%', height: '100%' }}
          />
        </Box>
      }
      weight="thin"
    />
    <ProgressCircle
      styles={{
        progress: {
          stroke: assets.btc.color,
        },
      }}
      progress={0.24}
      size={32}
      contentNode={
        <Box height="100%" padding={0.25} width="100%">
          <RemoteImage
            alt={assets.btc.name}
            shape="circle"
            source={assets.btc.imageUrl}
            style={{ width: '100%', height: '100%' }}
          />
        </Box>
      }
      weight="thin"
    />
    <ProgressCircle
      styles={{
        progress: {
          stroke: assets.btc.color,
        },
      }}
      progress={0.24}
      size={40}
      contentNode={
        <Box height="100%" padding={0.25} width="100%">
          <RemoteImage
            alt={assets.btc.name}
            shape="circle"
            source={assets.btc.imageUrl}
            style={{ width: '100%', height: '100%' }}
          />
        </Box>
      }
      weight="thin"
    />
    <ProgressCircle
      styles={{
        progress: {
          stroke: assets.btc.color,
        },
      }}
      progress={0.24}
      size={48}
      contentNode={
        <Box height="100%" padding={0.25} width="100%">
          <RemoteImage
            alt={assets.btc.name}
            shape="circle"
            source={assets.btc.imageUrl}
            style={{ width: '100%', height: '100%' }}
          />
        </Box>
      }
      weight="thin"
    />
    <ProgressCircle
      styles={{
        progress: {
          stroke: assets.btc.color,
        },
      }}
      progress={0.24}
      size={56}
      contentNode={
        <Box height="100%" padding={0.25} width="100%">
          <RemoteImage
            alt={assets.btc.name}
            shape="circle"
            source={assets.btc.imageUrl}
            style={{ width: '100%', height: '100%' }}
          />
        </Box>
      }
      weight="thin"
    />
  </HStack>
</VStack>

Custom Text Color

The progress circle's default content can be customized to display a custom text color.

Loading...
Live Code
<HStack gap={2}>
  <ProgressCircle
    color="fgPrimary"
    progress={0.2}
    size={100}
    contentNode={<DefaultProgressCircleContent color="fgPrimary" progress={0.2} />}
  />
  <ProgressCircle
    color="fgPositive"
    progress={0.4}
    size={100}
    contentNode={<DefaultProgressCircleContent color="fgPositive" progress={0.4} />}
  />
</HStack>

Custom Styles

The progress circle can be customized with styles and class names.

Loading...
Live Code
<HStack gap={2}>
  <ProgressCircle
    progress={0.4}
    size={100}
    styles={{
      circle: {
        stroke: 'transparent',
      },
    }}
    contentNode={<TextTitle1 color="fgPrimary">40%</TextTitle1>}
    weight="semiheavy"
  />
  <ProgressCircle
    color="fgPositive"
    progress={0.6}
    size={100}
    styles={{
      progress: {
        strokeLinecap: 'square',
      },
    }}
    contentNode={<Icon color="fgPositive" name="circleCheckmark" size="l" />}
  />
</HStack>

Interactive Demo

This is for demo purposes. ProgressContainerWithButtons isn't designed for production usage.

Loading...
Live Code
<ProgressContainerWithButtons>
  {({ calculateProgress }) => (
    <HStack gap={2}>
      <ProgressCircle progress={calculateProgress(0)} size={100} />
      <ProgressCircle progress={calculateProgress(0.2)} size={100} />
    </HStack>
  )}
</ProgressContainerWithButtons>

Animation Callbacks

You can use the onAnimationStart and onAnimationEnd callbacks to track the progress of the animation.

Loading...
Live Code
function Example() {
  const [animationStatus, setAnimationStatus] = React.useState('Ready');

  const handleAnimationStart = useCallback(() => {
    setAnimationStatus('Animating...');
  }, []);

  const handleAnimationEnd = useCallback(() => {
    setAnimationStatus('Animation Ended');
  }, []);

  return (
    <ProgressContainerWithButtons>
      {({ calculateProgress }) => (
        <VStack gap={2}>
          <Text>Animation Status: {animationStatus}</Text>
          <ProgressCircle
            onAnimationEnd={handleAnimationEnd}
            onAnimationStart={handleAnimationStart}
            progress={calculateProgress(0.2)}
            size={100}
          />
        </VStack>
      )}
    </ProgressContainerWithButtons>
  );
}

Is this page useful?

Coinbase Design is an open-source, adaptable system of guidelines, components, and tools that aid the best practices of user interface design for crypto products.