MeterGroup

A segmented bar showing how parts make up a whole.

Storage90%
Documents42%Photos27%Videos13%Other8%
<script setup lang="ts">
  import { MeterGroup, type MeterItem } from '@hina-ui/vue'

  const items: MeterItem[] = [
    { label: 'Documents', value: 42 },
    { label: 'Photos', value: 27 },
    { label: 'Videos', value: 13 },
    { label: 'Other', value: 8 },
  ]
</script>

<template>
  <MeterGroup label="Storage" :items="items" class="w-96" />
</template>
vue

Usage

import { MeterGroup } from '@hina-ui/vue'
ts

Each entry in items has a label and a value, and may set a tone; max is the total, 100 by default. The segments sit side by side on one track in proportion to their values, and the legend below lists each segment’s label and value.

Read56%Reading19%
<script setup lang="ts">
  import { MeterGroup, type MeterItem } from '@hina-ui/vue'

  const items: MeterItem[] = [
    { label: 'Read', value: 56 },
    { label: 'Reading', value: 19 },
  ]
</script>

<template>
  <MeterGroup :items="items" class="w-96" />
</template>
vue

Examples

Label and total

label sits above the track, with the sum of the segments at the end of the same line.

This year's books87%
Read56%Reading19%To read12%
<script setup lang="ts">
  import { MeterGroup, type MeterItem } from '@hina-ui/vue'

  const items: MeterItem[] = [
    { label: 'Read', value: 56 },
    { label: 'Reading', value: 19 },
    { label: 'To read', value: 12 },
  ]
</script>

<template>
  <MeterGroup label="This year's books" :items="items" class="w-96" />
</template>
vue

Changing values

When several values change at once, each segment’s width transitions smoothly to its new share, and the legend updates with it.

Storage90%
Documents42%Photos27%Videos13%Other8%
<script setup lang="ts">
  import { computed, ref } from 'vue'
  import { Button, MeterGroup, Stack, type MeterItem } from '@hina-ui/vue'

  const labels = ['Documents', 'Photos', 'Videos', 'Other']
  const sets = [
    [42, 27, 13, 8],
    [12, 36, 31, 6],
    [25, 8, 48, 14],
  ]
  const index = ref(0)

  const items = computed<MeterItem[]>(() =>
    labels.map((label, i) => ({ label, value: sets[index.value]![i]! })),
  )
</script>

<template>
  <Stack gap="md" align="start" class="w-96">
    <MeterGroup label="Storage" :items="items" class="w-full" />
    <Button size="sm" variant="soft" tone="neutral" @click="index = (index + 1) % sets.length">
      Next data set
    </Button>
  </Stack>
</template>
vue

Tones

Segments without a tone take the accent, info, success, warning, danger and neutral colours in that order; a tone can also be set per segment.

Review results92%
Approved62%Pending21%Returned9%
<script setup lang="ts">
  import { MeterGroup, type MeterItem } from '@hina-ui/vue'

  const items: MeterItem[] = [
    { label: 'Approved', value: 62, tone: 'success' },
    { label: 'Pending', value: 21, tone: 'warning' },
    { label: 'Returned', value: 9, tone: 'danger' },
  ]
</script>

<template>
  <MeterGroup label="Review results" :items="items" class="w-96" />
</template>
vue

Value format

format receives a segment’s value and the total and returns the text to show. The same text is what assistive technology reads as the value.

Storage108 GB
Documents38 GBPhotos24 GBVideos46 GB
<script setup lang="ts">
  import { MeterGroup, type MeterItem } from '@hina-ui/vue'

  const items: MeterItem[] = [
    { label: 'Documents', value: 38 },
    { label: 'Photos', value: 24 },
    { label: 'Videos', value: 46 },
  ]

  const format = (value: number) => `${value} GB`
</script>

<template>
  <MeterGroup label="Storage" :items="items" :max="256" :format="format" class="w-96" />
</template>
vue

Sizes

Three heights, the same as Progress, md by default.

<script setup lang="ts">
  import { MeterGroup, Stack, type MeterItem } from '@hina-ui/vue'

  const items: MeterItem[] = [
    { label: 'Read', value: 56 },
    { label: 'Reading', value: 19 },
  ]
</script>

<template>
  <Stack gap="lg" class="w-96">
    <MeterGroup :items="items" size="sm" :legend="false" />
    <MeterGroup :items="items" :legend="false" />
    <MeterGroup :items="items" size="lg" :legend="false" />
  </Stack>
</template>
vue

Without a legend

Set legend to false to keep only the track.

Storage82%
<script setup lang="ts">
  import { MeterGroup, type MeterItem } from '@hina-ui/vue'

  const items: MeterItem[] = [
    { label: 'Documents', value: 42 },
    { label: 'Photos', value: 27 },
    { label: 'Videos', value: 13 },
  ]
</script>

<template>
  <MeterGroup label="Storage" :items="items" :legend="false" class="w-96" />
</template>
vue

Behavior

  • Segment widths transition smoothly when values change, with duration and easing from the motion variables.
  • An out-of-range value is clamped: below 0 counts as 0, above max counts as max.
  • When the segments add up to more than max, the excess is clipped by the track; each segment’s value and width still correspond.

Accessibility

  • Each segment carries role="meter", named by its label, with aria-valuenow, aria-valuemin and aria-valuemax following the value and max.
  • The track carries role="group", named by label.
  • With format the result is also written to each segment’s aria-valuetext.
  • The dots in the legend are hidden from assistive technology; labels and values are plain text.

API

Props

Prop
Type
Default
Description
items
MeterItem[]
—
Required. The segments
max
number
100
Total
label
string
—
Label above the track
legend
boolean
true
Whether to show the legend
format
(value: number, max: number) => string
percentage
Format of the value text
size
'sm' | 'md' | 'lg'
'md'
Height
class
string
—
Extra classes on the root element

Other attributes fall through to the track element carrying role="group".

Types

Field
Type
Description
label
string
Required. Label of the segment
value
number
Required. Value of the segment
tone
'accent' | 'neutral' | 'success' | 'warning' | 'danger' | 'info'
Colour of the segment, assigned in order