ProgressGroup New

GitHub
A progress bar split into multiple segments that add up to a total.

Usage

Use the ProgressGroup component to display multiple values as segments of a single progress bar.

  • System19%
  • Apps6%
  • Documents9%
  • Multimedia33%
<script setup lang="ts">
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'System',
    value: 24,
    color: 'neutral',
    icon: 'i-lucide-cog'
  },
  {
    label: 'Apps',
    value: 8,
    color: 'error',
    icon: 'i-lucide-app-window'
  },
  {
    label: 'Documents',
    value: 12,
    color: 'warning',
    icon: 'i-lucide-file'
  },
  {
    label: 'Multimedia',
    value: 42,
    color: 'success',
    icon: 'i-lucide-film'
  }
])
</script>

<template>
  <UProgressGroup :max="128" :items="items" class="w-96" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'System',
    value: 24,
    color: 'neutral',
    icon: 'i-lucide-cog'
  },
  {
    label: 'Apps',
    value: 8,
    color: 'error',
    icon: 'i-lucide-app-window'
  },
  {
    label: 'Documents',
    value: 12,
    color: 'warning',
    icon: 'i-lucide-file'
  },
  {
    label: 'Multimedia',
    value: 42,
    color: 'success',
    icon: 'i-lucide-film'
  }
])
</script>

<template>
  <UProgressGroup :max="128" :items="items" class="w-96" />
</template>

Items

Use the items prop as an array of objects with the following properties:

  • Compute42%
  • Storage18%
  • Bandwidth9%
<script setup lang="ts">
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'Compute',
    value: 42,
    color: 'primary'
  },
  {
    label: 'Storage',
    value: 18,
    color: 'info'
  },
  {
    label: 'Bandwidth',
    value: 9,
    color: 'warning'
  }
])
</script>

<template>
  <UProgressGroup :items="items" class="w-96" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'Compute',
    value: 42,
    color: 'primary'
  },
  {
    label: 'Storage',
    value: 18,
    color: 'info'
  },
  {
    label: 'Bandwidth',
    value: 9,
    color: 'warning'
  }
])
</script>

<template>
  <UProgressGroup :items="items" class="w-96" />
</template>
Items without an icon get a colored dot in the list instead.

Max

Use the max prop to set the value all items add up to. Defaults to 100.

  • Used25%
  • Reserved13%
<script setup lang="ts">
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'Used',
    value: 128,
    color: 'primary'
  },
  {
    label: 'Reserved',
    value: 64,
    color: 'neutral'
  }
])
</script>

<template>
  <UProgressGroup :max="512" :items="items" class="w-96" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'Used',
    value: 128,
    color: 'primary'
  },
  {
    label: 'Reserved',
    value: 64,
    color: 'neutral'
  }
])
</script>

<template>
  <UProgressGroup :max="512" :items="items" class="w-96" />
</template>
Values are clamped between 0 and max, and segments that add up to more than max share the track proportionally.

Status

Use the status prop to display the summed value above the bar.

58%
  • System19%
  • Apps6%
  • Multimedia33%
<script setup lang="ts">
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'System',
    value: 24,
    color: 'neutral'
  },
  {
    label: 'Apps',
    value: 8,
    color: 'error'
  },
  {
    label: 'Multimedia',
    value: 42,
    color: 'success'
  }
])
</script>

<template>
  <UProgressGroup status :max="128" :items="items" class="w-96" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'System',
    value: 24,
    color: 'neutral'
  },
  {
    label: 'Apps',
    value: 8,
    color: 'error'
  },
  {
    label: 'Multimedia',
    value: 42,
    color: 'success'
  }
])
</script>

<template>
  <UProgressGroup status :max="128" :items="items" class="w-96" />
</template>
The status tracks the end of the bar, use :ui="{ status: 'w-full' }" to make it span the full width instead.

Color

Use the color prop to change the color of every segment that doesn't set its own.

  • Read42%
  • Write18%
<script setup lang="ts">
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'Read',
    value: 42
  },
  {
    label: 'Write',
    value: 18
  }
])
</script>

<template>
  <UProgressGroup color="neutral" :items="items" class="w-96" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'Read',
    value: 42
  },
  {
    label: 'Write',
    value: 18
  }
])
</script>

<template>
  <UProgressGroup color="neutral" :items="items" class="w-96" />
</template>
Both this prop and each item's color accept any CSS color value, which is handy for palettes outside the theme.

Size

Use the size prop to change the size of the ProgressGroup.

  • Read42%
  • Write18%
<script setup lang="ts">
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'Read',
    value: 42,
    color: 'primary'
  },
  {
    label: 'Write',
    value: 18,
    color: 'info'
  }
])
</script>

<template>
  <UProgressGroup size="xl" :items="items" class="w-96" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'Read',
    value: 42,
    color: 'primary'
  },
  {
    label: 'Write',
    value: 18,
    color: 'info'
  }
])
</script>

<template>
  <UProgressGroup size="xl" :items="items" class="w-96" />
</template>

Orientation

Use the orientation prop to change the orientation of the ProgressGroup. Defaults to horizontal.

  • Read42%
  • Write18%
<script setup lang="ts">
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'Read',
    value: 42,
    color: 'primary'
  },
  {
    label: 'Write',
    value: 18,
    color: 'info'
  }
])
</script>

<template>
  <UProgressGroup orientation="vertical" :items="items" class="h-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ProgressGroupItem } from '@nuxt/ui'

const items = ref<ProgressGroupItem[]>([
  {
    label: 'Read',
    value: 42,
    color: 'primary'
  },
  {
    label: 'Write',
    value: 18,
    color: 'info'
  }
])
</script>

<template>
  <UProgressGroup orientation="vertical" :items="items" class="h-48" />
</template>

Examples

With status slot

Use the #status slot to replace the summed percentage with your own content.

86GB used

42GB remaining

  • System19%
  • Apps6%
  • Documents9%
  • Multimedia33%
<script setup lang="ts">
import type { ProgressGroupItem } from '@nuxt/ui'

const max = 128

const items: ProgressGroupItem[] = [
  { label: 'System', value: 24, color: 'neutral', icon: 'i-lucide-cog' },
  { label: 'Apps', value: 8, color: 'error', icon: 'i-lucide-app-window' },
  { label: 'Documents', value: 12, color: 'warning', icon: 'i-lucide-file' },
  { label: 'Multimedia', value: 42, color: 'success', icon: 'i-lucide-film' }
]

const used = items.reduce((total, item) => total + (item.value ?? 0), 0)
</script>

<template>
  <UProgressGroup :items="items" :max="max" status class="w-96" :ui="{ status: 'w-full justify-between' }">
    <template #status>
      <p>{{ used }}GB used</p>
      <p class="text-muted">
        {{ max - used }}GB remaining
      </p>
    </template>
  </UProgressGroup>
</template>

With item slots

Use the #item-label and #item-trailing slots to change what each entry displays. Both receive the item, its index and its percent.

  • System24GB
  • Apps8GB
  • Documents12GB
  • Multimedia42GB
<script setup lang="ts">
import type { ProgressGroupItem } from '@nuxt/ui'

const items: ProgressGroupItem[] = [
  { label: 'System', value: 24, color: 'neutral' },
  { label: 'Apps', value: 8, color: 'error' },
  { label: 'Documents', value: 12, color: 'warning' },
  { label: 'Multimedia', value: 42, color: 'success' }
]
</script>

<template>
  <UProgressGroup :items="items" :max="128" class="w-96">
    <template #item-label="{ item }">
      <span class="font-medium">{{ item.label }}</span>
    </template>

    <template #item-trailing="{ item }">
      {{ item.value }}GB
    </template>
  </UProgressGroup>
</template>

With custom colors

Give each item a CSS color to build a breakdown outside the theme palette.

70% Full

~89.7K / 128K Tokens

  • System prompt4.2K
  • Tool definitions18.4K
  • Rules12.8K
  • Skills7.1K
  • MCP & dynamic tools17.1K
  • Subagent definitions5.5K
  • Conversation24.6K
<script setup lang="ts">
import type { ProgressGroupItem } from '@nuxt/ui'

const max = 128

const items: ProgressGroupItem[] = [
  { label: 'System prompt', value: 4.2, color: 'var(--color-neutral-400)' },
  { label: 'Tool definitions', value: 18.4, color: 'var(--color-violet-400)' },
  { label: 'Rules', value: 12.8, color: 'var(--color-green-400)' },
  { label: 'Skills', value: 7.1, color: 'var(--color-amber-400)' },
  { label: 'MCP & dynamic tools', value: 17.1, color: 'var(--color-rose-400)' },
  { label: 'Subagent definitions', value: 5.5, color: 'var(--color-sky-400)' },
  { label: 'Conversation', value: 24.6, color: 'var(--color-orange-400)' }
]

const used = items.reduce((total, item) => total + (item.value ?? 0), 0)
</script>

<template>
  <UProgressGroup :items="items" :max="max" status class="w-96" :ui="{ status: 'w-full justify-between' }">
    <template #status="{ percent }">
      <p>{{ percent }}% Full</p>
      <p class="text-muted">
        ~{{ used.toFixed(1) }}K / {{ max }}K Tokens
      </p>
    </template>

    <template #item-trailing="{ item }">
      {{ item.value }}K
    </template>
  </UProgressGroup>
</template>

API

Props

Prop Default Type
as'div'any

The element or component this component should render as.

items T[]
max100 number

The value all items add up to, used to compute each segment's share of the track.

statusboolean

Display the summed progress value.

size'md' "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl"
color'primary' "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" | string

Any theme color, or any CSS color value for palettes outside the theme.

orientation'horizontal' "horizontal" | "vertical"

The orientation of the progress bar.

ui { root?: SlotClass; base?: SlotClass; segment?: SlotClass; indicator?: SlotClass; status?: SlotClass; list?: SlotClass; item?: SlotClass; itemLeadingIcon?: SlotClass; itemLeadingDot?: SlotClass; itemLabel?: SlotClass; itemTrailing?: SlotClass; }

Slots

Slot Type
status{ percent: number; }
item{ item: T; index: number; percent: number; }
item-leading{ item: T; index: number; percent: number; }
item-label{ item: T; index: number; percent: number; }
item-trailing{ item: T; index: number; percent: number; }

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    progressGroup: {
      slots: {
        root: 'gap-2',
        base: 'flex overflow-hidden rounded-full bg-accented',
        segment: 'duration-200 ease-out motion-reduce:transition-none',
        indicator: 'size-full',
        status: 'flex text-dimmed duration-200 ease-out motion-reduce:transition-none',
        list: 'flex flex-col gap-1',
        item: 'flex items-center gap-1.5 min-w-0',
        itemLeadingIcon: 'shrink-0',
        itemLeadingDot: 'shrink-0 rounded-full',
        itemLabel: 'truncate',
        itemTrailing: 'ms-auto shrink-0 text-dimmed'
      },
      variants: {
        color: {
          primary: {
            indicator: 'bg-primary',
            itemLeadingIcon: 'text-primary',
            itemLeadingDot: 'bg-primary'
          },
          secondary: {
            indicator: 'bg-secondary',
            itemLeadingIcon: 'text-secondary',
            itemLeadingDot: 'bg-secondary'
          },
          success: {
            indicator: 'bg-success',
            itemLeadingIcon: 'text-success',
            itemLeadingDot: 'bg-success'
          },
          info: {
            indicator: 'bg-info',
            itemLeadingIcon: 'text-info',
            itemLeadingDot: 'bg-info'
          },
          warning: {
            indicator: 'bg-warning',
            itemLeadingIcon: 'text-warning',
            itemLeadingDot: 'bg-warning'
          },
          error: {
            indicator: 'bg-error',
            itemLeadingIcon: 'text-error',
            itemLeadingDot: 'bg-error'
          },
          neutral: {
            indicator: 'bg-inverted',
            itemLeadingIcon: 'text-highlighted',
            itemLeadingDot: 'bg-inverted'
          }
        },
        size: {
          '2xs': {
            status: 'text-xs',
            list: 'text-xs',
            itemLeadingIcon: 'size-3',
            itemLeadingDot: 'size-1.5'
          },
          xs: {
            status: 'text-xs',
            list: 'text-xs',
            itemLeadingIcon: 'size-3',
            itemLeadingDot: 'size-1.5'
          },
          sm: {
            status: 'text-sm',
            list: 'text-sm',
            itemLeadingIcon: 'size-4',
            itemLeadingDot: 'size-2'
          },
          md: {
            status: 'text-sm',
            list: 'text-sm',
            itemLeadingIcon: 'size-4',
            itemLeadingDot: 'size-2'
          },
          lg: {
            status: 'text-sm',
            list: 'text-sm',
            itemLeadingIcon: 'size-4',
            itemLeadingDot: 'size-2'
          },
          xl: {
            status: 'text-base',
            list: 'text-base',
            itemLeadingIcon: 'size-5',
            itemLeadingDot: 'size-2.5'
          },
          '2xl': {
            status: 'text-base',
            list: 'text-base',
            itemLeadingIcon: 'size-5',
            itemLeadingDot: 'size-2.5'
          }
        },
        orientation: {
          horizontal: {
            root: 'w-full flex flex-col',
            base: 'w-full flex-row',
            segment: 'h-full transition-[width]',
            status: 'flex-row items-center justify-end w-(--percent) min-w-fit transition-[width]'
          },
          vertical: {
            root: 'h-full flex flex-row',
            base: 'h-full flex-col',
            segment: 'w-full transition-[height]',
            status: 'flex-col justify-end h-(--percent) min-h-fit transition-[height]'
          }
        }
      },
      compoundVariants: [
        {
          orientation: 'horizontal',
          size: '2xs',
          class: 'h-px'
        },
        {
          orientation: 'horizontal',
          size: 'xs',
          class: 'h-0.5'
        },
        {
          orientation: 'horizontal',
          size: 'sm',
          class: 'h-1'
        },
        {
          orientation: 'horizontal',
          size: 'md',
          class: 'h-2'
        },
        {
          orientation: 'horizontal',
          size: 'lg',
          class: 'h-3'
        },
        {
          orientation: 'horizontal',
          size: 'xl',
          class: 'h-4'
        },
        {
          orientation: 'horizontal',
          size: '2xl',
          class: 'h-5'
        },
        {
          orientation: 'vertical',
          size: '2xs',
          class: 'w-px'
        },
        {
          orientation: 'vertical',
          size: 'xs',
          class: 'w-0.5'
        },
        {
          orientation: 'vertical',
          size: 'sm',
          class: 'w-1'
        },
        {
          orientation: 'vertical',
          size: 'md',
          class: 'w-2'
        },
        {
          orientation: 'vertical',
          size: 'lg',
          class: 'w-3'
        },
        {
          orientation: 'vertical',
          size: 'xl',
          class: 'w-4'
        },
        {
          orientation: 'vertical',
          size: '2xl',
          class: 'w-5'
        }
      ],
      defaultVariants: {
        color: 'primary',
        size: 'md'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        progressGroup: {
          slots: {
            root: 'gap-2',
            base: 'flex overflow-hidden rounded-full bg-accented',
            segment: 'duration-200 ease-out motion-reduce:transition-none',
            indicator: 'size-full',
            status: 'flex text-dimmed duration-200 ease-out motion-reduce:transition-none',
            list: 'flex flex-col gap-1',
            item: 'flex items-center gap-1.5 min-w-0',
            itemLeadingIcon: 'shrink-0',
            itemLeadingDot: 'shrink-0 rounded-full',
            itemLabel: 'truncate',
            itemTrailing: 'ms-auto shrink-0 text-dimmed'
          },
          variants: {
            color: {
              primary: {
                indicator: 'bg-primary',
                itemLeadingIcon: 'text-primary',
                itemLeadingDot: 'bg-primary'
              },
              secondary: {
                indicator: 'bg-secondary',
                itemLeadingIcon: 'text-secondary',
                itemLeadingDot: 'bg-secondary'
              },
              success: {
                indicator: 'bg-success',
                itemLeadingIcon: 'text-success',
                itemLeadingDot: 'bg-success'
              },
              info: {
                indicator: 'bg-info',
                itemLeadingIcon: 'text-info',
                itemLeadingDot: 'bg-info'
              },
              warning: {
                indicator: 'bg-warning',
                itemLeadingIcon: 'text-warning',
                itemLeadingDot: 'bg-warning'
              },
              error: {
                indicator: 'bg-error',
                itemLeadingIcon: 'text-error',
                itemLeadingDot: 'bg-error'
              },
              neutral: {
                indicator: 'bg-inverted',
                itemLeadingIcon: 'text-highlighted',
                itemLeadingDot: 'bg-inverted'
              }
            },
            size: {
              '2xs': {
                status: 'text-xs',
                list: 'text-xs',
                itemLeadingIcon: 'size-3',
                itemLeadingDot: 'size-1.5'
              },
              xs: {
                status: 'text-xs',
                list: 'text-xs',
                itemLeadingIcon: 'size-3',
                itemLeadingDot: 'size-1.5'
              },
              sm: {
                status: 'text-sm',
                list: 'text-sm',
                itemLeadingIcon: 'size-4',
                itemLeadingDot: 'size-2'
              },
              md: {
                status: 'text-sm',
                list: 'text-sm',
                itemLeadingIcon: 'size-4',
                itemLeadingDot: 'size-2'
              },
              lg: {
                status: 'text-sm',
                list: 'text-sm',
                itemLeadingIcon: 'size-4',
                itemLeadingDot: 'size-2'
              },
              xl: {
                status: 'text-base',
                list: 'text-base',
                itemLeadingIcon: 'size-5',
                itemLeadingDot: 'size-2.5'
              },
              '2xl': {
                status: 'text-base',
                list: 'text-base',
                itemLeadingIcon: 'size-5',
                itemLeadingDot: 'size-2.5'
              }
            },
            orientation: {
              horizontal: {
                root: 'w-full flex flex-col',
                base: 'w-full flex-row',
                segment: 'h-full transition-[width]',
                status: 'flex-row items-center justify-end w-(--percent) min-w-fit transition-[width]'
              },
              vertical: {
                root: 'h-full flex flex-row',
                base: 'h-full flex-col',
                segment: 'w-full transition-[height]',
                status: 'flex-col justify-end h-(--percent) min-h-fit transition-[height]'
              }
            }
          },
          compoundVariants: [
            {
              orientation: 'horizontal',
              size: '2xs',
              class: 'h-px'
            },
            {
              orientation: 'horizontal',
              size: 'xs',
              class: 'h-0.5'
            },
            {
              orientation: 'horizontal',
              size: 'sm',
              class: 'h-1'
            },
            {
              orientation: 'horizontal',
              size: 'md',
              class: 'h-2'
            },
            {
              orientation: 'horizontal',
              size: 'lg',
              class: 'h-3'
            },
            {
              orientation: 'horizontal',
              size: 'xl',
              class: 'h-4'
            },
            {
              orientation: 'horizontal',
              size: '2xl',
              class: 'h-5'
            },
            {
              orientation: 'vertical',
              size: '2xs',
              class: 'w-px'
            },
            {
              orientation: 'vertical',
              size: 'xs',
              class: 'w-0.5'
            },
            {
              orientation: 'vertical',
              size: 'sm',
              class: 'w-1'
            },
            {
              orientation: 'vertical',
              size: 'md',
              class: 'w-2'
            },
            {
              orientation: 'vertical',
              size: 'lg',
              class: 'w-3'
            },
            {
              orientation: 'vertical',
              size: 'xl',
              class: 'w-4'
            },
            {
              orientation: 'vertical',
              size: '2xl',
              class: 'w-5'
            }
          ],
          defaultVariants: {
            color: 'primary',
            size: 'md'
          }
        }
      }
    })
  ]
})

Changelog

No recent changes