๐ŸŸข Progress

Features


Basic

use NProgress to display a progress bar.

<script setup lang="ts">
const progress = ref(0)

const { pause } = useTimeoutPoll(fetchData, 100, { immediate: true })

async function fetchData() {
  if (progress.value === 100)
    return pause()

  progress.value++
}
</script>

<template>
  <NProgress
    v-model="progress"
  />
</template>

Color

progress="{color}" - change the color of the progress.

You can use any color provided by the Tailwind CSS color palette, the default is primary. You can also add your own colors to the palette through the Configuration section.
<template>
  <div class="flex flex-col space-y-4">
    <NProgress
      :model-value="23"
      progress="primary"
    />

    <NProgress
      :model-value="54"
      progress="error"
    />

    <NProgress
      :model-value="60"
      progress="indigo"
    />

    <NProgress
      :model-value="45"
      progress="orange"
    />
  </div>
</template>

Indeterminate

By not providing a modeValue prop, the progress will be indeterminate. This means that the progress will be animated without a specific value.

<template>
  <div class="flex flex-col space-y-4">
    <NProgress
      progress="primary"
    />

    <NProgress
      progress="error"
    />

    <NProgress
      progress="indigo"
    />

    <NProgress
      progress="orange"
    />
  </div>
</template>

Size

size="{size}" - change the size of the progress.

๐Ÿš€ You can freely adjust the size of the progress using any size imaginable. No limits exist, and you can use breakpoints such as sm:sm, xs:lg to change size based on screen size or states such as hover:lg, focus:3xl to change size based on input state and more.

The height and width of the progress scale depends on the progress-size. If you want to change the height and width simultaneously, you can always customize it using utility classes.
<template>
  <div class="flex flex-col space-y-4">
    <NProgress
      :model-value="23"
      size="xs"
    />

    <NProgress
      :model-value="54"
      progress="error"
      size="25px lg:3px"
    />

    <NProgress
      :model-value="10"
      progress="lime"
      size="2cm"
    />
  </div>
</template>

Customization

You can customize the progress using the una prop and utility classes.

You can also globally customize the progress preset if you want to have a different default style. See Configuration section for more details.
<script setup lang="ts">
const value = ref(30)

watchEffect((cleanupFn) => {
const timer = setTimeout(() => value.value = 60, 500)
cleanupFn(() => clearTimeout(timer))

const timer2 = setTimeout(() => value.value = 90, 1000)
cleanupFn(() => clearTimeout(timer2))
})
</script>

<template>
<div>
  <NProgress
    v-model="value"
    :una="{
      progressRoot: 'bg-gray-200 rounded-none h-3',
      progressIndicator: 'bg-blue-500 animate-pulse',
    }"
  />
</div>
</template>

Slots

You can use the following slots to customize the progress.

NameDescriptionProps
defaultThe progress indicator.-

Props

import type { ProgressRootProps } from 'radix-vue'
import type { HTMLAttributes } from 'vue'

type Extensions = ProgressRootProps & { class?: HTMLAttributes['class'] }

export interface NProgressProps extends Extensions {
  /**
   * Allows you to add `UnaUI` progress preset properties,
   * Think of it as a shortcut for adding options or variants to the preset if available.
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/progress.ts
   * @example
   * progress="red""
   */
  progress?: string
  /**
   * Allows you to change the size of the progress.
   *
   * @default base|md
   *
   * @example
   * size="sm" | size="2cm" | size="2rem" | size="2px"
   */
  size?: string
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/progress.ts
   */
  una?: {
    // components
    progressRoot?: HTMLAttributes['class']
    progressIndicator?: HTMLAttributes['class']
  }
}

Presets

import type { RuleContext } from '@unocss/core'
import type { Theme } from '@unocss/preset-uno'
import { parseColor } from '@unocss/preset-mini/utils'

type ProgressPrefix = 'progress'

export const staticProgress: Record<`${ProgressPrefix}-${string}`, string> = {
  // base
  'progress-indeterminate': 'absolute bg-brand h-full',

  // components
  'progress-root': 'relative h-0.5em w-full overflow-hidden rounded-full bg-brand/20',
  'progress-indicator': 'h-full w-full flex-1 bg-brand transition-all',
}

export const dynamicProgress = [
  [/^progress-(.*)$/, ([, body]: string[], { theme }: RuleContext<Theme>) => {
    const color = parseColor(body, theme)
    if ((color?.cssColor?.type === 'rgb' || color?.cssColor?.type === 'rgba') && color.cssColor.components)
      return `n-${body}-600 dark:n-${body}-500`
  }],
]
export const progress = [
  ...dynamicProgress,
  staticProgress,
]

Component

<script setup lang="ts">
import type { NProgressProps } from '../../types'
import {
  ProgressIndicator,
  ProgressRoot,
} from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../utils'

const props = withDefaults(
  defineProps<NProgressProps>(),
  {
    progress: 'primary',
  },
)

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})
</script>

<template>
  <ProgressRoot
    v-bind="delegatedProps"
    :class="
      cn(
        'progress-root',
        props.una?.progressRoot,
        props.class,
      )
    "
    :progress
  >
    <slot>
      <ProgressIndicator
        v-if="props.modelValue !== undefined || props.modelValue === null"
        :class="cn(
          'progress-indicator',
          props.una?.progressIndicator,
        )"
        :style="`transform: translateX(-${100 - (props.modelValue ?? 0)}%);`"
      />
      <template
        v-else
      >
        <ProgressIndicator
          :class="cn(
            'increase progress-indeterminate',
            props.una?.progressIndicator,
          )"
        />
        <ProgressIndicator
          :class="cn(
            'decrease progress-indeterminate',
            props.una?.progressIndicator,
          )"
        />
      </template>
    </slot>
  </ProgressRoot>
</template>

<style scoped>
.increase.progress-indeterminate {
  animation: progress-increase 2s ease-in-out infinite;
}

.decrease.progress-indeterminate  {
  animation: progress-indeterminate-decrease 2s 0.9s ease-in-out infinite;
}

@keyframes progress-decrease {
  0% {
    left: -90%;
    width: 90%;
  }

  100% {
    left: 110%;
    width: 10%;
  }
}

@keyframes progress-increase {
  0% {
    left: -5%;
    width: 5%;
  }

  100% {
    left: 130%;
    width: 150%;
  }
}
</style>