Ranked list
A top-N list: a leading tile, a label, a value and a proportional bar.
<script setup lang="ts">
import type { RankedItem } from '@shared/components/common/charts/RankedList.vue'
import RankedList from '@shared/components/common/charts/RankedList.vue'
const items: RankedItem[] = [
{ id: '1', label: 'Amina Haddad', sublabel: 'Enterprise', value: '$182,400', percent: 100 },
{ id: '2', label: 'Luca Rossi', sublabel: 'Mid-market', value: '$146,900', percent: 81 },
{ id: '3', label: 'Mei Tanaka', sublabel: 'Enterprise', value: '$121,300', percent: 67 },
{ id: '4', label: 'Pedro Alves', sublabel: 'SMB', value: '$88,050', percent: 48 },
{ id: '5', label: 'Sara Lindqvist', sublabel: 'SMB', value: '$61,700', percent: 34 },
]
</script>
<template>
<RankedList :items="items" class="max-w-md" />
</template>Usage
items is RankedItem[]. value is a string you have already formatted, and percent is the bar fill from 0 to 100. Whether that is a share of the total or of the largest row is your decision, and the component clamps it either way so a raw ratio cannot overflow the track.
It is deliberately not a chart. Nothing here imports ECharts, so it can go on any page without pulling the charting bundle along.
With no icon, the tile shows the label's initials. They are segmented by grapheme, so a name that starts with an emoji or a combining mark is not cut in half, and they are uppercased in the active language.
Examples
Icons and accents
icon takes a Lucide icon and accent is one of primary, sky, emerald, amber, violet or rose, the same six the stat cards use.
<script setup lang="ts">
import type { RankedItem } from '@shared/components/common/charts/RankedList.vue'
import RankedList from '@shared/components/common/charts/RankedList.vue'
import { Package, ShoppingCart, Truck, Wrench } from 'lucide-vue-next'
const items: RankedItem[] = [
{ id: 'orders', label: 'Orders', value: '4,812', percent: 100, icon: ShoppingCart, accent: 'sky' },
{ id: 'shipments', label: 'Shipments', value: '4,106', percent: 85, icon: Truck, accent: 'emerald' },
{ id: 'stock', label: 'Stock moves', value: '2,940', percent: 61, icon: Package, accent: 'amber' },
{ id: 'repairs', label: 'Repairs', value: '618', percent: 13, icon: Wrench, accent: 'rose' },
]
</script>
<template>
<RankedList :items="items" class="max-w-md" />
</template>Loading and empty
loading renders five skeleton lines. An empty list shows "Nothing to show yet" rather than collapsing to nothing.
<script setup lang="ts">
import type { RankedItem } from '@shared/components/common/charts/RankedList.vue'
import RankedList from '@shared/components/common/charts/RankedList.vue'
import { Button } from '@shared/components/ui/button'
import { computed, ref } from 'vue'
const ITEMS: RankedItem[] = [
{ id: '1', label: 'Northwind', value: '312 units', percent: 100 },
{ id: '2', label: 'Lumen Retail', value: '188 units', percent: 60 },
]
const state = ref<'loading' | 'empty' | 'items'>('loading')
const items = computed(() => (state.value === 'items' ? ITEMS : []))
</script>
<template>
<div class="max-w-md space-y-3">
<div class="flex flex-wrap gap-2">
<Button size="sm" :variant="state === 'loading' ? 'default' : 'outline'" @click="state = 'loading'">
Loading
</Button>
<Button size="sm" :variant="state === 'empty' ? 'default' : 'outline'" @click="state = 'empty'">
Empty
</Button>
<Button size="sm" :variant="state === 'items' ? 'default' : 'outline'" @click="state = 'items'">
With items
</Button>
</div>
<RankedList :items="items" :loading="state === 'loading'" />
</div>
</template>API
Source: src/shared/components/common/charts/RankedList.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | RankedItem[] | required | The rows, in the order you want them. |
loading | boolean | false | Renders five skeleton lines. |
RankedItem
| Key | Type | Description |
|---|---|---|
id | string | The list key. |
label | string | The first line, and the source of the initials. |
sublabel | string | The second line: an owner, a category. |
value | string | The right-aligned figure, already formatted. |
percent | number | The bar fill, 0 to 100. Clamped. |
icon | LucideIcon | Replaces the initials. |
accent | 'primary' | 'sky' | 'emerald' | 'amber' | 'violet' | 'rose' | The tile color. Defaults to primary. |
Emits
None.
Slots
None.
Accessibility
- The rows are a real list, so a screen reader announces how many there are.
- The bar is
role="presentation". The number it stands for is already printed beside it, and announcing the bar too would only be noise. - The tile is
aria-hidden, because initials and an icon add nothing to the label next to them. - Labels and sublabels truncate rather than wrap, so a long name cannot push the value out of the row.