Category bars

Where the monthly total goes, one row per category: the name, the amount, and a bar as long as the amount is large. It answers a different question from the trend line above it, and the difference is the whole reason it exists: the line says how the total moved, these say what it is made of.

Organism2 grey pages2 coloured5 rows1 axis: noneno states, not interactive

Why it was built, 2026-08-19

History and trends carried the sentence "By category. Streaming is up $6 since March" and nothing else. The founder read it and said: "я так понимаю должен бути график со столбиками по категориями а его нет". They were right twice over. There was no picture where a picture belongs, and the sentence was also measuring the wrong window: it said since March under a three-month view that begins in May.

So the sentence became a picture and stayed a sentence. The bars are the composition now, which does not change when the range changes; the line under them is the change over the chosen range, and it is the only part of the block the range control touches. Two facts, one block, and neither is doing the other's job.

Anatomy

A list, and inside every row three slots: the name, the amount atom, and the track. Everything below is drawn by design/system/components/category-bars.css through the same entry point a product screen loads, so this page cannot drift from the screen: there is no second copy of the CSS here to drift from. The five figures are the five category subtotals on Home and they sum to $192.90.

  • Streaming$54.96
  • Software$53.98
  • News$36.00
  • Fitness$24.98
  • Music$22.98

Measured on this page: rows at 8px of padding with a 1px --line-divider between them, the track 8px tall with a 6px radius, the name at 14px in --text-body and the amount at 14px in --text-primary. The fill is --edge, 3.22:1 against the track in the light theme and 4.65:1 in the dark, which clears the 3:1 a non-text shape has to clear. Against the surface the track sits on it is 3.46 and 5.15, so the bar is visible whether or not the eye finds the track.

Behaviour at width

Fluid, and it has no point and no threshold. The name column takes what is left after the amount, the track takes the whole row. It is correct at 360 and correct in a 546px column beside another list, which is where the product actually puts it. See Width.

The length is derived, and that is the point of the component

Each row carries data-value, the same figure its own amount prints. design/system/behaviour.js reads the list, finds the largest value in it, and sets every rect as a share of that. Nothing about the arithmetic lives in a screen file.

Against the largest and never against the total. Five categories that sum to the total would each take a fifth of the width at most, and the two biggest, 54.96 and 53.98, would sit a hair apart at about 28 per cent of a column: the picture would be five short stubs and the one fact it exists to show would be invisible. Against the largest they are 100 and 98.2, which is exactly the reading: these two are the same size and everything else is smaller.

CategoryAmountShare of the largestShare of the total
Streaming$54.9610028.5
Software$53.9898.228.0
News$36.0065.518.7
Fitness$24.9845.513.0
Music$22.9841.811.9

The markup carries the answer too, and that is not a duplicate. The rect width written into the page is what draws with no script running, computed by the same arithmetic, exactly as the plot's d is on the chart placeholder. The script recomputes it and writes over it, so the file that owns the geometry is the file that reads the data, and a hand-kept percentage beside a hand-kept amount cannot drift apart the first time a price changes.

The bar is not petrol, and that is a decision

It is --bg-measure, which resolves to --edge, the founder's non-text contrast value and the only neutral in the system guaranteed to be seen as a shape rather than as a line.

D-Concept spends petrol on one element inside a screen's content, and on History and trends that element is the plotted line, which the founder made petrol on 2026-08-18. A bar chart in the same colour, directly under that line, would make the screen argue with itself about which picture is the important one. The answer is the line: the bars say where the money goes, the line says how the total moved, and only the second is what a person opens this screen for. The ground is in tokens.css beside the token.

One quiet neutral, whatever the row
  • Streaming$54.96
  • Music$22.98

Every bar is the same colour. Length is the only variable, which is what makes five of them comparable at a glance.

Never a colour per category
Streaming in petrol, Software in amber, News in clay

A colour per category is a legend the person has to learn, and in this system amber and clay already mean a price changed and something failed. Spending them on "this row is Music" would make a category look like a problem, which is the one thing this product never does.

When to use it

Use it for a small, closed set of parts of one whole, where the reading is which parts are large and which are small. Five categories is the set it was built for and the set the product has.

Do not use it for a ranking that has no whole, and do not use it for time. A quantity over months is the chart placeholder, which is what the same screen puts above it; a list of things with a status each is the subscription row. The test is whether the numbers add up to something a person would recognise. Here they add up to $192.90, which is the figure on Home.

States

None, and that is a decision rather than an omission. No row is a link, a button or a field on any page in the product, and category-bars.css declares no :hover, no :focus-visible and no [disabled]. Making a category clickable would promise a per-category screen, and there is no node for one.

The loading state is the empty track, and it needs nothing from this file. On the wait the rows carry no data-value, so the script leaves them alone and the track draws with no fill: an empty track is already what a bar looks like before its figure arrives, so there is no grey block standing in for one.

The technical half

ReadsWhich tokenGrowing from
the fill of a bar--bg-measure--edge, the founder's non-text contrast decision. New on 2026-08-19 and in both themes. A role and not a reuse: the bar is a quantity, and no existing role in this system meant that
the track under it--bg-recessed--panel, the recessed step. The same ground the chart placeholder's frame stands on when it has nothing to draw
the name--text-body--slate. Sentence case, not the eyebrow: the block already has one uppercase label, its group head, and a second tier of caps under it would make the category names compete with it
the amountnone of this file'sthe amount atom, whose fifth host this is
the line between rows--line-divider--hairline-soft, the softer of the two: it divides content and encloses nothing
geometry--radius-xs6px, the chip and the skeleton. On an 8px track it reads as a rounded end, which is what a measure wants and what a rule does not

Lives in design/system/components/category-bars.css, with the arithmetic in design/system/behaviour.js under THE CATEGORY BARS. Stands on 2 grey pages and 2 coloured ones: History and Trends with its five figures, and the wait with five empty tracks and no figures at all.

<ul class="bars"> <li data-value="54.96"> <span class="name">Streaming</span> <span class="amt">$54.96</span> <span class="bar" aria-hidden="true"><svg viewBox="0 0 100 8" preserveAspectRatio="none" focusable="false"><rect width="100" height="8"/></svg></span> </li> </ul> data-value is the figure, and the only one this component needs. The rect width is the same arithmetic, for a page with no script.

Limits

U15, one content measure per screen. This block states no width. It is as wide as the column it stands in, which on History and trends is one half of a .grid.roomy, and it never asks for a measure of its own. See Architecture.

Five rows is the set, not a limit that is enforced. The arithmetic works for any number of rows, but the block was designed against a closed set that fits without scrolling beside another list. A category taxonomy that grew past about eight would need a different answer, and inventing that answer before there is a taxonomy is how a system gets a variant nobody uses.