Skip to content
Nizivo UI

RankedList

Top products, channels or regions as an ordered list, each row a label, its value and a bar relative to the leader. The bars are decorative; the values are printed.

Server component, no client JS

Import

import { RankedList } from '@nizivo/react/ranked-list';

// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css and @nizivo/react/css/card.css and @nizivo/react/css/inline-error.css and @nizivo/react/css/number-value.css and @nizivo/react/css/skeleton.css
import '@nizivo/react/css/ranked-list.css';

Static or interactive

Server. It ships no client JavaScript when a Server Component renders it, in every Next.js build mode; its code is bundled only when a client component (a 'use client' file) imports it. The bars are SVG with percentage widths, so the server HTML has no style attribute. How to choose.

In a client app (a Vite or React Router single-page app, or a 'use client' file) it is an ordinary React component: event handlers, refs and state work as usual. In a client app.

Examples

Basic

Figma's "Top channels": five rows, bars relative to the leader, "View all" as a real link.

Top channels

View all
  1. Online store$64,120
  2. Instagram Shop$21,804
  3. Wholesale$18,240
  4. Pop-up market$9,312
  5. Marketplace$6,050
import type { ComponentPropsWithRef, JSX } from 'react';
import { RankedList } from '@nizivo/react/ranked-list';

/** Stands in for a router link (Next `Link`, React Router `Link`). */
function Link({ children, ...props }: ComponentPropsWithRef<'a'>): JSX.Element {
  return <a {...props}>{children}</a>;
}

const CHANNELS = [
  ['Online store', 64120],
  ['Instagram Shop', 21804],
  ['Wholesale', 18240],
  ['Pop-up market', 9312],
  ['Marketplace', 6050],
] as const;

const USD = { style: 'currency', currency: 'USD', maximumFractionDigits: 0 } as const;

export function RankedListExample(): JSX.Element {
  return (
    <RankedList.Root
      title="Top channels"
      action={<RankedList.Action render={<Link href="/channels" />}>View all</RankedList.Action>}
      format={USD}
    >
      {CHANNELS.map(([label, value]) => (
        <RankedList.Item key={label} label={label} value={value} />
      ))}
    </RankedList.Root>
  );
}

States

The Suspense fallback is the same list with status="loading": it reserves five rows, so the box does not move when the data streams in (§16.5). Empty and error keep the same box.

Top channels

Loading Top channels

Top cities

No data

Top products

Could not load Top products
import type { JSX } from 'react';
import { RankedList } from '@nizivo/react/ranked-list';

export function RankedListStatesExample(): JSX.Element {
  return (
    <div>
      <RankedList.Root title="Top channels" status="loading" />
      <RankedList.Root title="Top cities" status="empty" />
      <RankedList.Root title="Top products" status="error" />
    </div>
  );
}

Parts

Use the parts through the namespace (RankedList.Root) or by their flat names (RankedListRoot); both work from Server Components.

RankedList.Root

Server component Renders data-slot="ranked-list" with the class nz-ranked-list. Props: RankedListRootProps. Source: src/ranked-list/ranked-list.tsx.

NameTypeDefaultDescription
actionReactNode–

The header's end, for example <RankedList.Action href="/channels">View all</RankedList.Action>.

childrenReactNode–

RankedList.Item elements, the leader first.

emptyReactNode–

Replaces the empty message.

errorReactNode–

Replaces the error message, for example an InlineError with a retry action.

formatIntl.NumberFormatOptions–

Every item's number format, unless the item sets its own (Intl.NumberFormatOptions).

labelsPartial<RankedListLabels>–
localestring'en-US'

BCP 47 tag for every item's value.

maxnumberthe largest value among the direct `RankedList.Item` children (the leader); pass it when the items are wrapped in your own components

The value a full bar stands for.

pendingbooleanfalse

A refresh is in flight while the rows show: data-pending and aria-busy (ready only, as in Metric).

renderRenderProp<RankedListRootState>–

Composition of the root (Card's surface through the nz-card class).

rowsnumber5

How many rows loading, empty and error reserve, so the box does not change when the data arrives. Pass the number of rows you show (Figma: 5; "max ~6 rows, then View all").

statusDataStatus
"ready""loading""empty""error"
'ready'

The data lifecycle (§16.5). A ready list with no items is empty.

titleReactNode–

The heading ("Top channels"); it also names the list.

titleLevelRankedListTitleLevel
23456
3

The heading level; its look is Heading/H4 at every level.

  • Also takes every attribute of <div> except title; children is redeclared above. (Omit<ComponentPropsWithRef<"div">, "title">)

render takes an element, or, in client code, a function of the props and this state (RankedListRootState):

NameTypeDefaultDescription
pendingrequiredboolean–
statusrequiredDataStatus
"ready""loading""empty""error"
–

RankedList.Item

Server component Renders data-slot="ranked-list-item" with the class nz-ranked-list-item. Props: RankedListItemProps. Source: src/ranked-list/ranked-list.tsx.

NameTypeDefaultDescription
formatIntl.NumberFormatOptions–

Overrides the list's format.

labelrequiredReactNode–
localestringthe list's `locale`, else 'en-US'
maxnumber–

Overrides the list's max for this row.

renderRenderProp<RankedListItemState>–
valuerequirednumber–
  • Also takes every attribute of <li> except children; value is redeclared above. (Omit<ComponentPropsWithRef<"li">, "children">)

render takes an element, or, in client code, a function of the props and this state (RankedListItemState):

NameTypeDefaultDescription
ratiorequirednumber–

value ÷ max, from 0 to 1.

RankedList.Action

"View all": a real link, at least 24 px tall (WCAG 2.5.8; figma/04 issue 7).

Server component Renders data-slot="ranked-list-action" with the class nz-ranked-list-action. Props: RankedListActionProps. Source: src/ranked-list/ranked-list.tsx.

NameTypeDefaultDescription
renderRenderProp<Record<string, never>>–

The router's link, for example render={<Link href="/channels" />}. It must render an <a>.

  • Also takes every attribute of <a>. (ComponentPropsWithRef<"a">)

render takes an element, or, in client code, a function of the props and no state.

Other exports

RankedListActionProps type

import type { RankedListActionProps } from '@nizivo/react/ranked-list'

The props of RankedList.Action, listed above.

RankedListItemProps type

import type { RankedListItemProps } from '@nizivo/react/ranked-list'

The props of RankedList.Item, listed above.

RankedListItemState type

import type { RankedListItemState } from '@nizivo/react/ranked-list'

The render state of RankedList.Item, listed above.

RankedListLabels type

import type { RankedListLabels } from '@nizivo/react/ranked-list'

RankedList's own strings (§16.4): serialisable, English defaults, Metric's wording. {label} is replaced by the title (left out when there is none).

NameTypeDefaultDescription
emptyrequiredstring'No data'

Shown when there is nothing to rank and no empty slot.

errorrequiredstring'Could not load {label}'

The InlineError message on a load failure with no error slot.

loadingrequiredstring'Loading {label}'

Static, visually hidden text while loading (§16.5: no live region).

RankedListRootProps type

import type { RankedListRootProps } from '@nizivo/react/ranked-list'

The props of RankedList.Root, listed above.

RankedListRootState type

import type { RankedListRootState } from '@nizivo/react/ranked-list'

The render state of RankedList.Root, listed above.

RankedListTitleLevel type

import type { RankedListTitleLevel } from '@nizivo/react/ranked-list'

type RankedListTitleLevel = 2 | 3 | 4 | 5 | 6

Data attributes and slots

Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.

SlotRendered byData attributes
ranked-list
.nz-ranked-list
RankedList.Root
root (takes render)
none
ranked-list-action
.nz-ranked-list-action
RankedList.Action
root (takes render); interactive, focus: self
none
ranked-list-bar
.nz-ranked-list-bar
RankedList.Item
inner element (CSS only)
none
ranked-list-header
.nz-ranked-list-header
RankedList.Root
inner element (CSS only)
none
ranked-list-item
.nz-ranked-list-item
RankedList.Item
root (takes render)
none
ranked-list-label
.nz-ranked-list-label
RankedList.Item
inner element (CSS only)
none
ranked-list-list
.nz-ranked-list-list
RankedList.Root
inner element (CSS only)
none
ranked-list-message
.nz-ranked-list-message
RankedList.Root
inner element (CSS only)
none
ranked-list-placeholder
.nz-ranked-list-placeholder
RankedList.Root
inner element (CSS only)
none
ranked-list-status
.nz-ranked-list-status
RankedList.Root
inner element (CSS only)
none
ranked-list-title
.nz-ranked-list-title
RankedList.Root
inner element (CSS only)
none
ranked-list-track
.nz-ranked-list-track
RankedList.Item
inner element (CSS only)
none

CSS hooks

Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.

HookDefaultRead as
--nz-ranked-list-action-fg--nz-text-brand
  • color on .nz-ranked-list-action
--nz-ranked-list-bar-fill--nz-chart-primary
  • fill on .nz-ranked-list-fill
--nz-ranked-list-empty-fg--nz-text-secondary
  • color on .nz-ranked-list-empty
--nz-ranked-list-gap--nz-space-4
  • gap on .nz-card.nz-ranked-list
  • gap on .nz-ranked-list-list
  • gap on .nz-ranked-list-reserve
--nz-ranked-list-label-fg--nz-text-primary
  • color on .nz-ranked-list-label
--nz-ranked-list-title-font--nz-font-heading-h4
  • font on .nz-ranked-list-title
--nz-ranked-list-track-bg--nz-chart-track
  • background on .nz-ranked-list-track
--nz-ranked-list-track-size--nz-space-1-5
  • block-size on .nz-ranked-list-track
--nz-ranked-list-value-fg--nz-text-primary
  • color on .nz-ranked-list-value

Layers: nz.components. Classes: .nz-card, .nz-ranked-list, .nz-ranked-list-action, .nz-ranked-list-bar, .nz-ranked-list-empty, .nz-ranked-list-fill, .nz-ranked-list-header, .nz-ranked-list-item, .nz-ranked-list-label, .nz-ranked-list-list, .nz-ranked-list-message, .nz-ranked-list-placeholder, .nz-ranked-list-reserve, .nz-ranked-list-row, .nz-ranked-list-skeleton-label, .nz-ranked-list-skeleton-value, .nz-ranked-list-status, .nz-ranked-list-title, .nz-ranked-list-track, .nz-ranked-list-value.

Tokens this stylesheet reads (20)

--nz-chart-primary, --nz-chart-track, --nz-font-body-m, --nz-font-heading-h4, --nz-font-label-m, --nz-font-numeric-m, --nz-radius-full, --nz-radius-xs, --nz-space-1, --nz-space-1-5, --nz-space-2, --nz-space-3, --nz-space-4, --nz-space-6, --nz-text-brand, --nz-text-primary, --nz-text-secondary, --nz-tracking-heading-h4, --nz-tracking-label-m, --nz-tracking-numeric-m

Accessibility

These parts are interactive. Each gets the focus outline and touch-action rule from @nizivo/react/css/base.css (the nz.base layer), so the floor survives a restyle:

  • ranked-list-action: draws the focus outline itself on keyboard focus.

Keyboard

KeysAction
Tab, Shift+TabMoves to and from the RankedList.Action link ("View all"), as on any link. The rows are not Tab stops.
EnterFollows the focused link.

Source: a native <a> (RankedListAction in ranked-list.tsx, no client code), at least 24 px tall (WCAG 2.5.8).

Figma

Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.

On this page