Skip to content
Dethink Components

ComponentsBar List

Bar List

The fastest way to answer “which ones matter most?”. Labels sit on the bars, values align on the right, and long lists fold behind Show more.

On this page

Install component source from the public registry, or use the local workspace package. Follow the setup guide first. The npm package is not published yet.

View registry files

Implemented with React and browser APIs, without an additional behavior or rendering library.

Shared foundation: React, clsx for conditional classes, tailwind-merge for resolving utility-class conflicts. Styles use Tailwind CSS and Dethink CSS variables.

Based on this component’s source and registry composition. Libraries used only in showcase examples are excluded.

Import the component into your page or component file.

Usage
import { BarList } from "@dethink/components";

Linked top pages with a limit, clickable sources on a shared percent scale, and currency values with icons.

Top pages

Rows link to detail views. Long paths truncate with the full text available on hover and to assistive technology.

Show sourceexamples/bar-list/top-pages.tsx
examples/bar-list/top-pages.tsx
"use client";

import { BarList } from "@dethink/components";

const pages = [
  { label: "/", value: 12_940, href: "#home" },
  { label: "/pricing", value: 5_320, href: "#pricing" },
  { label: "/docs/getting-started", value: 4_870, href: "#docs" },
  {
    label: "/blog/launching-dethink-charts-without-a-chart-library",
    value: 2_310,
    href: "#blog",
  },
  { label: "/changelog", value: 1_420, href: "#changelog" },
  { label: "/careers", value: 640, href: "#careers" },
  { label: "/legal/privacy", value: 210, href: "#privacy" },
];

export function BarListTopPages() {
  return (
    <div className="w-full max-w-xl">
      <h3 id="top-pages-heading" className="mb-3 text-sm font-medium">
        Top pages
      </h3>
      <BarList
        aria-labelledby="top-pages-heading"
        data={pages}
        labelHeader="Page"
        valueHeader="Visitors"
        limit={5}
      />
    </div>
  );
}

Filter by source

onItemClick turns rows into buttons. The selected source takes its own palette slot, and max={1} puts shares on a common scale.

Filtering by Organic search. Bars share a 0–100% scale.

Show sourceexamples/bar-list/sources.tsx
examples/bar-list/sources.tsx
"use client";

import { BarList, type BarListItem } from "@dethink/components";
import { Globe, Mail, MessageCircle, Search } from "lucide-react";
import { useState } from "react";

const sources: BarListItem[] = [
  { key: "search", label: "Organic search", value: 0.412, icon: <Search /> },
  { key: "direct", label: "Direct", value: 0.268, icon: <Globe /> },
  { key: "social", label: "Social", value: 0.174, icon: <MessageCircle /> },
  { key: "email", label: "Email", value: 0.146, icon: <Mail /> },
];

export function BarListSources() {
  const [selected, setSelected] = useState("search");

  return (
    <div className="grid w-full max-w-xl gap-4">
      <BarList
        aria-label="Traffic by source"
        data={sources.map((item) => ({
          ...item,
          color: item.key === selected ? "chart-3" : undefined,
        }))}
        color="chart-1"
        max={1}
        formatValue={(value) => `${(value * 100).toFixed(1)}%`}
        onItemClick={(item) => setSelected(item.key!)}
        size="sm"
      />
      <p className="text-muted-foreground text-xs" aria-live="polite">
        Filtering by{" "}
        <span className="text-foreground font-medium">
          {sources.find((item) => item.key === selected)?.label}
        </span>
        . Bars share a 0–100% scale.
      </p>
    </div>
  );
}

Currency, icons and sort order

Values format compactly as currency. Ascending sort surfaces the smallest markets first.

CountryRevenue
  • United States$48.2K
  • Germany$21.4K
  • India$19.9K
  • United Kingdom$14.1K
  • Brazil$9,310
CountryRevenue
  • Brazil$9,310
  • United Kingdom$14.1K
  • India$19.9K
  • Germany$21.4K
  • United States$48.2K
Show sourceexamples/bar-list/countries.tsx
examples/bar-list/countries.tsx
"use client";

import { BarList } from "@dethink/components";

const countries = [
  { label: "United States", value: 48_210, icon: "🇺🇸" },
  { label: "Germany", value: 21_430, icon: "🇩🇪" },
  { label: "India", value: 19_880, icon: "🇮🇳" },
  { label: "United Kingdom", value: 14_050, icon: "🇬🇧" },
  { label: "Brazil", value: 9_310, icon: "🇧🇷" },
];

export function BarListCountries() {
  return (
    <div className="grid w-full gap-8 sm:grid-cols-2">
      <BarList
        aria-label="Revenue by country"
        data={countries}
        color="chart-6"
        formatOptions={{ style: "currency", currency: "USD" }}
        labelHeader="Country"
        valueHeader="Revenue"
      />
      <BarList
        aria-label="Revenue by country, ascending"
        data={countries}
        color="chart-7"
        sort="ascending"
        formatOptions={{ style: "currency", currency: "USD" }}
        labelHeader="Country"
        valueHeader="Revenue"
      />
    </div>
  );
}
BarList props
PropWhat it doesDefault
dataArray<{ key?, label, value, href?, icon?, color? }>Rows to rank. href makes a row a link; color overrides the bar for that row.Not set
sort"descending" | "ascending" | "none"Ties always keep their input order."descending"
limitnumberRows shown before a Show more toggle.Not set
expanded / defaultExpanded / onExpandedChangeboolean / boolean / (expanded) => voidControlled or uncontrolled expansion of limited lists.— / false / —
color"chart-1" … "chart-8" | stringOne color for every bar. A single series stays one color."chart-1"
maxnumberValue that fills the track, for shares (1) or comparing lists on one scale.Largest value
formatValue / formatOptions(value) => string / Intl.NumberFormatOptions & { locale }Replace or tune the value formatter.compact
labelHeader / valueHeaderReactNodeColumn headings, such as "Page" and "Visitors".Not set
onItemClick(item, index) => voidTurns rows without an href into buttons, for drill-down or filtering.Not set
emptyLabel / showMoreLabel / showLessLabelReactNode / (hidden) => ReactNode / ReactNodeLocalize the empty state and toggle text."No data" / "Show N more" / "Show less"
size"sm" | "md"Row and bar height. Bars never exceed 24px."md"
animatebooleanBars grow from the start edge. Always off under reduced motion.true

BarList is a list: each row reads its label and value, and the bar is decorative because the value is always visible as text. Name the list with aria-label or aria-labelledby. Link and button rows are single tab stops with a visible focus ring. The Show more button reports its state with aria-expanded and controls the list. Values stay left-to-right in right-to-left layouts, and bars grow from the start edge.