Distribution
A ranked list of labels, each with a bar to scale, a count and its share of the whole.
Use it for a handful to a few dozen categories that people read as numbers: survey answers, traffic sources, ticket types, countries. Each row is the label with its bar drawn behind it, then the count, then the share of the whole.
The bar sits behind the label, so a long label keeps the full width and wraps instead of being cut. The counts and the shares are columns of one grid, so they line up down the list whatever their width.
Rows are drawn in the order you pass them. Sort them first. For one number and its trend use KPI, and for a single reading on a scale use Range Meter.
Installation
Copy the folder into components/ui, where you can edit it. It uses Button, Empty, Skeleton and the chart reveal from this library, the cn helper, and the bg-pattern-diagonal utility from the stylesheet.
components/ui/distribution/
├── Distribution.vue
├── DistributionSkeleton.vue
└── index.tsUsage
<script setup lang="ts">
import { Distribution } from "@/components/ui/distribution";
</script>
<template>
<Distribution
:items="[
{ label: 'Email', count: 4120 },
{ label: 'Search', count: 2310 },
{ label: 'Direct', count: 1488 },
]"
/>
</template>A long list
limit sets how many rows show before a Show more button. The default is 8. The button opens the rest in place and closes them again.
Colour
color tints the bars with a theme colour instead of the text colour, to match the charts around the list. The pattern and the edge take the same colour.
Something before each label
The leading slot draws into a fixed box before every label, a flag here. It receives the row, and row.item is the object you passed, so it can read your own fields. leading-class sizes the box; the default 24 by 16 fits a flag.
<Distribution :items="countries">
<template #leading="{ row }">
<Flag :country="row.item.code" :country-name="row.label" />
</template>
</Distribution>Progress inside a row
done is how many of a row's count are finished, drawn as a green fill inside the bar and written before the count. done-label names it for the tooltip and the screen reader. show-total adds a summary row under a rule, and total-done is passed rather than summed, because rows can count the same person twice.
A row against its own total
of gives a row its own denominator: the bar fills against it and the count reads "118 of 120". Use it when each row has a capacity of its own, such as seats in a room. When every row has the same of, it is left off the rows, so say it once in the card instead.
A rolled-up tail and links
other adds one muted row for everything past the top rows, labelled by other-label. A row with to links its label to its own page.
Top pages
Other languages
locale formats the counts and shares, and show-more-label, show-less-label, other-label, total-label and the empty texts carry the words, so the whole list can be written in the page's language.
Nothing to show
With no rows, or every row at zero, it shows an empty state from empty-title, empty-label and empty-icon instead of a column of zeros.
Loading
DistributionSkeleton draws the same grid at the same row height, so the card does not change size when the rows arrive. Pass the number of rows you expect.
API Reference
Distribution
DistributionSkeleton
Both components also take a class.
Notes
Columns
The share column is dropped when the list itself is narrower than 32rem, not when the screen is. Three lists side by side on a wide screen each get a third of it, and the bar already carries the proportion.
Motion
Only the done fill moves. It grows once, when the list first scrolls into view, each row 70ms after the one above. Under reduced motion it is drawn in place.




