Tripbooks design system / lab 11 / charts and settings

Lab 11, second pass / researched rather than assumed

Every measure, matched to its form.

You were right that the first pass picked chart types by habit. This one starts from the question a reader is actually asking, because the job the data has to do picks the form, and colour comes last. It also starts from a constraint I checked with a validator rather than an opinion.

The five dispositions tested as chart fills · light surface
PASSLightness band, all five inside the readable range
FAILChroma floor: resting reads as grey, correct for a status neutral and disqualifying as a series colour
WARNColour vision: won against theirs separates by 7.0 for protan eyes, legal only where a word sits beside the mark
WARNContrast: theirs 2.47 and resting 2.54 against the surface, under the 3 to 1 floor for an unlabelled fill

So the status palette cannot become a series palette. That is settled by measurement. Everything below is the design that follows from it.

01 / the research

What Tripbooks actually has to show, and the form each one earns.

Sixteen real measures from the platform. The middle column is the form, the right column is why that form and not the obvious one.

MeasureFormWhy this and not the obvious one
Booked revenue by monthcolumn, hatched tailMonths are discrete buckets a person compares, not a continuous signal, so columns and not an area. The current month is incomplete and must not look like a finished one: it takes a hatched fill, which is the same rule that has meant "open, not settled" since Lab 01.
Pipeline value, dailyarea, dashed projectionThis one is continuous. A pipeline has a value at every moment, so area is honest where it was wrong for months. Today is in progress, so the last stretch runs dashed over a hatched fill.
Conversion rate over timeline + reference bandA rate is a level, not a quantity, so it never gets an area fill: filling under a percentage implies accumulation that does not exist. The target range is a band behind the line, which answers "are we where we should be" without a second series.
Margin against item valuescatter + floor lineThe real question is "which items are sold too cheaply", which is a relationship between two measures, and no bar chart can show it. The margin floor is a reference line, so every item below it is visible without reading a number.
Revenue by destinationranked bars, emphasisLength already carries the order, so all bars share one colour and the top three are emphasised while the rest recede. Colouring by rank would repaint every survivor the moment somebody filters.
Service load by type and weekheatmapA grid where the cell value is the point. The only place on the platform where colour carries a quantity, using a single hue ramp checked for monotonic lightness.
Received, due, overdueone stacked barPart to whole with three parts. One bar, horizontal, with the figure inside each segment, because the contrast check says an unlabelled amber fill is not readable enough to stand alone.
Cash in against cash outdiverging columnsTwo directions around a zero baseline, which is what money moving in and out actually is. Two stacked series would hide the months where outflow beat inflow.
Enquiry to quotation to bookingfunnel primitiveAlready a platform component. One cohort narrowing, and each stage measured against the first rather than against itself.
Coverage against the gatemeterA single ratio against a limit. Not a chart and not a dial: a linear track where the threshold has a position, which a dial cannot do legibly.
Total spend, this monthstat tileA single current value with a movement and a spark. A one bar bar chart is a stat tile that took four times the space.
Revenue, all inhero figureThe number a page leads with. It is typography, not a graphic.
Quotation value, before and after a revisiondumbbellBefore and after per item. Two dots and a connector reads the change directly, where two grouped bars make a person do the subtraction.
Departures over the next 90 daysdensity stripOne row of days with load per day. It is a calendar problem wearing a chart's clothes, and the operational question is "which week is heavy", not "how many exactly".
Supplier concentrationranked bars + OtherThe question is "how exposed are we to one supplier", which is a top-of-list question. The tail folds into Other rather than becoming thirty more bars.
Revenue by country, on a maplocator onlyA map encodes area, so Kazakhstan outshouts Singapore whatever the revenue. Allowed to show where, never to show how much. The quantity goes beside it in a ranked list.

02 / built

Sales analytics, rebuilt on the research.

One currency at a time, unlike the dashboard which converts everything into the entity's base. A dashboard answers "how is the business", so one number is right. An analysis answers "where did it come from", and adding dollars to dirhams at today's rate to answer that is arithmetic dressed as insight.

CRM · Sales analyticsSM

Sales analytics

Northline Travel LLC USD onlyLast 12 monthsTable view
EnquiriesQuotationsBookings
Booked, 12 months
$11,385,000
14.2%
Bookings
428
37
Average booking
$26,600
3.1%
Margin
17.4%
0.8floor 8.0%

Booked revenue by month

USD · hatched month is still running
$1.5M$1.0M$500k0
September 2025$742,10028 bookings
October 2025$914,60033 bookings
November 2025$688,30026 bookings
December 2025$1,204,80041 bookings
January 2026$556,90021 bookings
February 2026$620,40024 bookings
March 2026$871,20032 bookings
April 2026$1,038,50037 bookings
May 2026$946,70034 bookings
June 2026$1,412,30048 bookings$1,412,300
July 2026$1,127,90040 bookings
August 2026, 20 days of 31$1,284,50041 bookings so far
SepOctNovDecJanFeb MarAprMayJunJulAug
June is the only labelled column because labelling twelve is noise and labelling none makes a reader hover for the answer they came for. August is hatched and it is 20 days of 31.

Live pipeline value

USD · 30 days · a stock, not a flow
$800k $600k $400k 0 $742,100 22 Jul 5 Aug 20 Aug
Solid through yesterday, dashed over hatch for the day still running. Area is right here and wrong for months, because a pipeline has a value at every moment and a month does not.

Enquiry to booking conversion

target band 45 to 55%
55% target ceiling 45% target floor 0 60% 48.8% Sep Feb Aug
No fill under the line. A rate is a level, not an accumulation, and shading beneath a percentage implies a quantity that does not exist. The band answers "are we where we should be" without inventing a second series.

Margin against item value

every active item · floor at 8.0%
30% 20% 10% 0 margin floor 8.0% Park Hyatt Kyoto · 2.0% Coach hire · −1.0% $0 item sell value $65k
Above the floor · 11 items
Below the floor, awaiting approval · 4 items
The only chart on the platform where the status palette marks individual points, and it earns it: below the floor is money the agency is losing, which is the second of the two exceptions where hue leaves disposition. The two worst are labelled, the rest are on hover.

Revenue by destination

a two city trip counts in both
Japan$2,184,40019%
Kenya$1,771,90016%
Italy$1,436,20013%
Portugal$1,132,70010%
Iceland$961,3008%
Vietnam$808,5007%
United Kingdom$634,1006%
31 others$2,455,90021%
Emphasis, not categorical. The top three carry the hue and the rest recede, because "where does our money come from" is answered by three names, not by thirty-eight colours.

Service load by type

tickets per week · colour carries the quantity
w26w27w28w29 w30w31w32w33 Change of date 2416 4832 Cancellation 1021 3210 Special request 4537 9643 Complaint 0112 1321 Rebooking 2354 2463 Refund request 1102 1211
0 9 tickets
Every cell prints its figure. A heatmap where you cannot read the number is a mood board.

Cash in against cash out

USD · six months · around a zero baseline
in out net −$121,400 Mar Apr May Jun Jul Aug
Received from customers
Paid to suppliers and staff
The one month where outflow beat inflow is labelled, because that is the entire reason a person opens this chart. Two stacked series would have hidden it.

Where the money stands

part to whole, three parts
$842,300 $220,900 $221,300
Received 65.6%
Due, within terms 17.2%
Overdue 17.2%
Figures sit inside the segments rather than in the legend alone, because the contrast check said an unlabelled amber fill is not readable enough to carry itself.

Table view

every chart on the platform has one
DestinationBookingsRevenueShareAverageMargin
Japan612,184,40019.0%35,80918.2%
Kenya441,771,90015.4%40,27021.4%
Italy581,436,20012.5%24,76216.1%
Portugal491,132,7009.8%23,11615.8%
Iceland31961,3008.4%31,01019.0%
Vietnam37808,5007.0%21,85117.7%
United Kingdom22634,1005.5%28,82314.3%
31 others1262,455,90021.4%19,49116.9%
All destinations42811,385,000100%26,60017.4%

03 / the forms we are not using

Six popular charts, and why none of them fits.

Worth writing down, because every one of these will be asked for eventually and the reason should not have to be reinvented each time.

FormVerdictReason
Pie, donut and ringnoAngle is the hardest quantity for a person to compare, and travel data is almost never two or three clean parts. A ring for a single proportion is defensible and a meter does that job better, with a threshold position a ring cannot show.
RadarnoThe axes have no natural order, so rotating them changes the shape and therefore the impression, without changing a single number.
Gauge and dialnoA curved scale wastes most of its area and cannot place a threshold precisely. The coverage meter is the same information as a straight line, and the gate needs an exact position.
Sunburst and treemapnoThe hierarchy is real, country then city then property, but nested area is unreadable below the second ring and impossible to label. Nested ranked lists say it, and the table says it exactly.
Dual axisneverTwo scales on one plot let the author choose where the lines cross, which means the picture is an editorial decision wearing a chart's clothes. Two measures of different scale get two charts or a common index.
Candlestickn/aThere is no open, high, low and close in this business. Listed for completeness so nobody spends a week on it.
What the reference gave us

Three ideas from it are now in the system and they are the good ones: a pattern fill instead of a gradient, which happens to be the hatch rule we already had; a dashed tail for the period still running, which fixed a genuine defect where August looked like a finished month next to July; and a reference band behind a series, which is how a target and a margin floor get stated without a second series. The biaxial variant we are not taking, for the reason above.

04 / measured against the stack we already run

What Recharts gives us, and what it does not.

The wireframes already run Recharts, and shadcn is a thin wrapper over it, so this is not a library evaluation. It is the question that actually matters: of the sixteen forms above, how many cost nothing to build.

Read from the live shadcn theme · its own default chart tokens
--chart-1lab(77.5% −6.5 −36.4)
--chart-2lab(54.2% 13.3 −74.7)
--chart-3lab(44.1% 29.0 −86.0)
--chart-4lab(36.9% 35.1 −85.7)
--chart-5lab(30.3% 27.8 −70.3)
The most useful thing on that page

Those five tokens are named like a categorical palette and they are not one. One hue family, lightness falling 77.5, 54.2, 44.1, 36.9, 30.3, in order. shadcn's own default for "five chart colours" is a sequential ramp, arrived at independently of the validator run at the top of this lab and landing in exactly the same place. Five slots called chart-1 to chart-5 is the shape everybody copies; a single hue going light to dark is what the people who built it actually shipped.

FormHow it gets builtCost
Column, monthly revenueBarChartNative. The hatched in-progress month is a Recharts pattern fill on one Cell, not a fork.
Area, pipeline valueAreaChartNative. The dashed projection is a second Area over the last segment with a dash array.
Line + target bandLineChart · ReferenceAreaNative, though not a shadcn preset. ReferenceArea is the band, ReferenceLine the edges.
Scatter + margin floorScatterChart · ReferenceLineNative in Recharts, absent from shadcn's gallery. Roughly thirty lines.
Ranked bars, emphasisBarChart layout verticalNative. Emphasis is a per-Cell fill, which is also why colour cannot follow rank: Cells are keyed by entity.
Stacked, part to wholeBarChart stackedNative. Labels inside segments are a LabelList.
Diverging, cash in and outBarChart · negative valuesNative. A ReferenceLine at zero and one series signed negative.
Sparkline in a stat tileLineChart, no axesNative.
Dumbbell, before and afterScatterChart + custom shapeRecharts can, awkwardly. Two divs and a rule is less code and reads the same. Plain HTML.
Funnelour own primitiveRecharts has a FunnelChart and we are not using it. Ours has to work at 52 pixels in a table cell and at board size from the same rule, which a chart component cannot do.
Heatmapnot in RechartsA CSS grid. Twelve lines, no dependency, and it prints.
Density strip, departuresnot in RechartsA CSS grid. It is a calendar, not a chart.
Coverage meter, the gatedeliberately not a chartPlain HTML. It has to sit inside a sticky 40 pixel bar and stay exact.
Stat tile and hero figuredeliberately not a chartTypography.
Map, locator onlyMapLibreAlready in the stack for other work. Shows where, never how much.
The answer in one line

Eight of the sixteen are native Recharts. Two more are Recharts with a reference primitive. The remaining six are plain HTML, and five of those are things that should never have been charts. Nothing in the chart language needs a new dependency, and nothing in it needs the platform to leave the stack it already runs.

Adoption decisionVerdictReason
ChartContainer, ChartTooltip, ChartLegendtakeA responsive wrapper, a themed tooltip and a legend that reads from one config object. This is the part worth having and it is a hundred lines we do not write.
accessibilityLayertakeOne prop, and it gives every chart keyboard navigation and screen reader output. The table view stays regardless, because a table is also how a figure gets checked and exported.
--chart-1 to --chart-5 as five categoriesredefineKeep the token names, because every Recharts example on the internet uses them, and fill them with the validated six step ramp instead of five unrelated hues. A developer who reaches for chart-3 then gets a ramp step rather than an accidental new meaning.
Status colours as chart tokensneverThe five dispositions live in their own token namespace and are never reachable from the chart config, so nothing can quietly become "series 4". That is the whole finding from the validator, enforced in the token layer rather than in a code review.
Animation on loadoffEvery series gets isAnimationActive false. A chart that animates in on a background tab paints nothing, and a board opened from a link must be right the instant it appears.

05 / the last archetype

A shape that survives fifty more settings.

One page, many sections, holding the rules every other page obeys. The grammar is a repeating three column row: what it is, what it is set to, and who is allowed to change it. That third column is what stops a settings page becoming the place where nobody knows who broke something.

· SettingsSM

Settings

Northline Travel LLC · United States · books in USD Search settings

Money and tax

Northline Travel LLC only · other entities set their own
Base currencyThe books are kept in this and it cannot change once a journal exists
USD · United States dollar
Locked · journals exist
Presentation currencyUsed by Consolidated Reporting only
USD
Admin
Commercial FX sourceRates used for quoting and settlement
Open Exchange Rateshourly
Admin
Statutory FX sourceRates used for the ledger and the tax return
Not set for the United States
Admin · blocks close
Tax regimeDecides the tax fields on every document
United States · sales tax by state
Admin
Period lockNothing can post on or before this date
31 Jul 2026
Finance
Margin floorItems sold below this route to an approver
8.0%
Finance
Rate stampingEvery cross currency movement keeps the rate it used, forever
always on
Not changeable

Roles and permissions

solid may · hatched may, with a second signature · empty may not
CapabilityAdminFinanceSupervisorAgentQuality
Create a quotationquotation.create
Clear an undercut marginquotation.approve
Issue an itembooking.issue
Release credit against the gatebooking.release
Open a passport copycustomer.passport
Read a private customercustomer.blacklist
Archive a customercustomer.archive
Post a manual journalledger.manual
Close a periodledger.close
Export a registerreports.export
The hatched cell is the interesting one

A tick and a blank can only say yes and no, and the true answer for releasing credit against the gate is "yes, with a second signature". Hatched has meant open, in flight, not settled alone since the first lab, and here it means a capability that exists but cannot be exercised by one person. Same mark, same meaning, on a surface nobody had it in mind for.

What I need from you

Four, and the archetype work is closed.

  1. The sixteen form choices. That table is the real deliverable of this lab. If any single mapping is wrong, it is wrong in a module we have not built yet, so now is when it costs nothing.
  2. Hatch for a period still running. August was drawn as a finished month in the first pass, which was a genuine defect. Now every incomplete period in the platform is hatched, and every projection dashed, using the rule from Lab 01.
  3. The six rejected forms. Written down with reasons so the answer does not have to be reinvented the next time somebody asks for a pie chart.
  4. The adoption decision. Take the shadcn wrapper and its accessibility layer, refill its five chart tokens with our validated ramp, and keep the status palette in a namespace the chart config cannot reach. Animation off on every series.
  5. On the two libraries. With the first one the chart frames were present and empty, which matches a problem recorded against it before: series start at zero opacity behind an animation frame. With shadcn the chart components never mounted in my browser pane at all, so I am making no rendering claim about it either way. Neither observation is a verdict on how either behaves inside a real application, and it does not matter much here, because the substantive answer is that we already run Recharts.