nili/ui
All components

ProgressBar

Overview
15 exports5 variant axes41 props4 files
import { ProgressBar, ProgressCircle } from '@nili/ui';

This is the library’s own overview page, rendered live. Switch the language or the theme in the header and it follows.

Basic

value is clamped to 0…max. A bare bar with no visible label needs an aria-label — there is nothing else in a table cell to name it.

Storage70%
Files7 of 10 files
import { ProgressBar } from "@nili/ui"

export function Example() {
  return (
    <>
      <ProgressBar value={35} aria-label={t('upload')} />
      <ProgressBar value={70} label={t('storage')} showValue />
      <ProgressBar value={7} max={10} label="Files" valueLabel="7 of 10 files" />
    </>
  )
}

Colors

Four colours. The colour is a signal, not decoration — warning and error should mean the number is bad, not merely high.

primary40%
success100%
warning85%
error95%
import { ProgressBar } from "@nili/ui"

export function Example() {
  return (
    <>
      <ProgressBar value={40} color="primary" label="Primary" />
      <ProgressBar value={100} color="success" label="Success" />
      <ProgressBar value={85} color="warning" label="Warning" />
      <ProgressBar value={95} color="error" label="Error" />
    </>
  )
}

Label position and actions

labelPosition is Figma's On Top / On Right, and it is logical — end follows reading direction. linkButton is the action under the bar.

top
Storage60%
end
Storage60%
with an action
Storage92%
Upgrade plan
import { ProgressBar, LinkButton } from "@nili/ui"

export function Example() {
  return (
    <>
      <ProgressBar value={60} label="Storage" labelPosition="top" showValue />
      <ProgressBar value={60} label="Storage" labelPosition="end" showValue />

      <ProgressBar
        value={92}
        color="warning"
        label="Storage"
        showValue
        linkButton={<LinkButton href="#" size="sm">Upgrade plan</LinkButton>}
      />
    </>
  )
}

Indeterminate

For work of unknown duration. It drops aria-valuenow, so assistive tech announces “busy” rather than inventing a percentage.

Syncing…
import { ProgressBar, ProgressCircle } from "@nili/ui"

export function Example() {
  return (
    <>
      <ProgressBar indeterminate label={t('indet.syncing')} />
      <ProgressCircle indeterminate aria-label={t('indet.loading')} />
    </>
  )
}

Progress Circle

The same API in a ring. Six sizes, and thickness in pixels when the default stroke is wrong for the context.

sizes
65%
80
65%
72
65%
64
65%
56
65%
48
65%
40
colors
40%
100%
85%
95%
thickness & custom centre
65%
65%
13/20
import { ProgressCircle } from "@nili/ui"

export function Example() {
  return (
    <>
      <ProgressCircle value={65} showValue aria-label="Complete" />
      <ProgressCircle value={65} size={80} showValue aria-label="Complete" />
      <ProgressCircle value={65} thickness={2} showValue aria-label="Complete" />

      {/* anything in the middle */}
      <ProgressCircle value={65} aria-label="Complete">
        <span className="text-[12px]">65/100</span>
      </ProgressCircle>
    </>
  )
}