Distribution

A ranked list of labels, each with a bar to scale, a count and its share of the whole.

Dark previewReplay previewOpen in new tab

Readers by source

Last 30 days

Email

4,120

Search

2,310

Direct

1,488

LinkedIn

932

Partner sites

410

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.

text
components/ui/distribution/
├── Distribution.vue
├── DistributionSkeleton.vue
└── index.ts

Usage

vue
<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.

Dark previewReplay previewOpen in new tab

Visitors by industry

Post-show survey

Service companies

2,204

Manufacturers and producers

1,803

End customers and homeowners

1,702

Distributors and wholesalers

1,201

Agents, dealers and retailers

1,004

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.

Dark previewReplay previewOpen in new tab

Visitors by industry

Post-show survey

Service companies

2,204

Manufacturers and producers

1,803

End customers and homeowners

1,702

Distributors and wholesalers

1,201

Agents, dealers and retailers

1,004

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.

vue
<Distribution :items="countries">
  <template #leading="{ row }">
    <Flag :country="row.item.code" :country-name="row.label" />
  </template>
</Distribution>
Dark previewReplay previewOpen in new tab

Visitors by country

5 of 18

IndonesiaIndonesia

28,410

SingaporeSingapore

1,204

MalaysiaMalaysia

986

ChinaChina

640

JapanJapan

312

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.

Dark previewReplay previewOpen in new tab

Check-in by ticket

Today

Day pass

1,512checked in of 1,840

Three-day pass

588checked in of 620

VIP

61checked in of 140

Student

402checked in of 410

Total

2,563checked in of 3,010

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.

Dark previewReplay previewOpen in new tab

Workshop seats

Day 2

Design systems at scale

118 of 120

Writing for interfaces

64 of 80

Motion that helps

52 of 150

Accessible forms

40 of 40

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.

Dark previewReplay previewOpen in new tab

Top pages

Views

9,120

7,402

3,310

2,108

Other (42 more)

2,876

Without the share

hide-share drops the percentage column, for lists whose rows have no honest base to divide by, such as days that each count a multi-day pass. total-label names the summary row.

Dark previewReplay previewOpen in new tab

Admissions by day

3 days

Friday

4,210

Saturday

6,930

Sunday

5,480

Admissions

16,620

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.

Dark previewReplay previewOpen in new tab

Pengunjung per jabatan

Survei

Pemilik usaha

3.120

Manajer

2.214

Arsitek

1.408

Kontraktor

1.302

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.

Dark previewReplay previewOpen in new tab

Answers

No answers yet

Rows appear once the first form comes in.

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.

Dark previewReplay previewOpen in new tab

Readers by source

API Reference

Distribution

PropTypeDefaultDescription
itemsreadonly DistributionItem[]—The rows, in order: { label, count, of?, muted?, to?, ariaLabel?, done?, doneNote? }. value is still read as the label when label is absent.
totalnumbersum of countsThe denominator of the share column.
limitnumber8Rows shown before the Show more button.
other{ buckets, count }—A muted last row for everything rolled up past the list.
formatValue(value: number) => stringgrouped digitsTurns a count into its label.
localestring"en-US"Locale of the default count format and of the share.
showTotalbooleanfalseAdds a summary row under a rule. Hidden when there is only one row.
totalLabelstring"Total"Label of the summary row.
totalDonenumber—The done figure of the summary row.
doneLabelstring"done"What done means, read after its figure: "checked in", "shipped".
color"neutral" | "primary" | "info" | "success" | "warning" | "destructive""neutral"Colour of the bars. neutral is a tint of the text colour.
hideSharebooleanfalseDrops the percentage column.
leadingClassstring"h-4 w-6"Size of the box the leading slot draws into.
animatedboolean | nullnullThe done fill's entrance. null follows the global chart animation switch.
emptyTitlestring"Nothing to show yet"Heading of the empty state.
emptyLabelstring"No answers yet"Sentence under it.
emptyIconstring"hugeicons:chart-histogram"Icon of the empty state.
showMoreLabel(hidden: number) => string"Show 5 more"Text of the Show more button.
showLessLabelstring"Show less"Text of the button once open.
otherLabel(buckets: number) => string"Other (42 more)"Label of the other row.
SlotDescription
leading{ row }. Drawn in a fixed box before each label.

DistributionSkeleton

PropTypeDefaultDescription
rowsnumber5How many rows to draw.
showTotalbooleanfalseAdds the rule and summary row, like Distribution.

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.