Design systemPhase 1 — Home & Cash Flow →Phase 2 — Analysis →Phase 3 — Ledger & Setup →
Fyro Finance
Phase 1 — Foundations

Design system

Tokens, type, number formatting and base components for the Fyro Finance app. Every colour is an HSL CSS variable compatible with the shadcn theme; every component below maps to a shadcn/ui primitive with lucide-react icons and Recharts for charts.

React + shadcn/ui
Tailwind CSS
lucide-react
Recharts
Geist / Geist Mono

01 — Brand

Wordmark, in three sizes and one collapsed mark for the compressed sidebar.

Fyro Finance
Fyro Finance
collapsed sidebar mark
Fyro Finance
On dark the mark uses --primary at 68% lightness so it keeps 4.5:1 against the app background.

02 — Colour tokens

Declared as bare HSL triples on :root and .dark, consumed as hsl(var(--token)).

Light — :root 11 core tokens
--background
0 0% 100%
--foreground
224 24% 12%
--card
0 0% 100%
--muted
222 20% 97%
--muted-foreground
222 10% 45%
--border
222 16% 90%
--primary
249 66% 54%
--primary-subtle
249 60% 96%
--positive
158 58% 33%
--negative / --destructive
354 63% 47%
--warning
33 88% 42%
Dark — .dark same token names
--background
226 24% 7%
--foreground
220 20% 96%
--card
226 20% 10%
--muted
226 16% 15%
--muted-foreground
222 12% 62%
--border
226 14% 19%
--primary
250 80% 68%
--primary-subtle
250 40% 18%
--positive
156 52% 52%
--negative / --destructive
354 78% 66%
--warning
36 88% 60%

03 — P&L section colours

One hue family for income, one for costs, neutral ink for subtotals. Sections are tinted at 4% saturation-weight on light and 14% on dark, so a 26-column table never turns into a pastel quilt. Colour carries the sign; the label carries the meaning.

Light
(+) Sales--pl-income
(−) Direct Costs--pl-cost-1
= Gross Direct Profit--pl-subtotal
(−) Operating Costs--pl-cost-2
= Gross Operating Profit--pl-subtotal
(−) Other Operating Costs--pl-cost-3
= EBITDA--pl-key
(+) Other Income--pl-income-2
(−) Additional Expenses--pl-cost-4
= Profit Net--pl-total
Dark
(+) Sales--pl-income
(−) Direct Costs--pl-cost-1
= Gross Direct Profit--pl-subtotal
(−) Operating Costs--pl-cost-2
= Gross Operating Profit--pl-subtotal
(−) Other Operating Costs--pl-cost-3
= EBITDA--pl-key
(+) Other Income--pl-income-2
(−) Additional Expenses--pl-cost-4
= Profit Net--pl-total
Cost centre palette — 10 slots
Fixed hue order, assigned once per cost centre and reused in every chart. Same index, same colour, everywhere.
Advertising
--cc-1
Software
--cc-2
Payroll
--cc-3
Consulting
--cc-4
Logistics
--cc-5
Banking
--cc-6
Office
--cc-7
Legal & Tax
--cc-8
Content
--cc-9
Travel
--cc-10
Other
--cc-rest

04 — Type, space, radius, elevation

Geist for the interface, Geist Mono for every figure. font-variant-numeric: tabular-nums is set on body, so columns align without extra classes.

display / 34 / 600
€ 482,610
kpi / 28 / 600
€ 74.2k
h1 / 22 / 600
Cash Flow — Monthly
h2 / 16 / 600
Operating Costs
body / 14 / 400
Default interface text and table labels.
num / 13 / 500 mono
1,234.56 · 98,410.00 · 1.086420
small / 12 / 400
Helper text, deltas, footnotes.
label / 11 / 600 caps
Column header
Spacing — 4px base
4
8
12
16
24
32
48
Card padding 24. Table cell 10/12. Page gutter 32 desktop, 16 mobile. Section gap 24.
Radius — --radius: 0.625rem
sm 4
md 6
lg 10
xl 14
Elevation — borders first, shadow only when floating
flat
sm
md
lg

05 — Number formatting

One rule set for the whole app, implemented once in formatMoney() / formatPct() on top of Intl.NumberFormat. The locale switch drives separators and symbol position; nothing is hand-formatted in a component.

Case EN — en-IE IT — it-IT Rule
Amount in a table € 1,234.56 1.234,56 € Always 2 decimals. Symbol before in EN, after in IT, non-breaking space between. Right-aligned, mono.
Negative −€ 8,420.00 −8.420,00 € Minus sign U+2212 leading, never parentheses, never red-on-red fill. --negative ink.
Cost row inside a (−) section € 8,420.00 8.420,00 € Magnitude only. The section header already carries the sign, so no double negative.
KPI, thousands € 74.2k 74,2k € Compact, 1 decimal, from €1,000. Exact value in the tooltip.
KPI, millions € 1.24M 1,24 Mln € Compact, 2 decimals, from €1,000,000.
Zero € 0.00 0,00 € A real zero is printed, in muted ink. Never blank.
No data — — Em dash, muted. Distinct from zero: nothing was imported for that cell.
Percentage / margin 18.4% 18,4% 1 decimal, no space before %.
Delta on a value +8.3% +8,3% Explicit sign, 1 decimal, coloured by direction. Over 999% → >999%. From zero → n/a.
Delta on a margin −1.6 pp −1,6 p.p. Percentages are compared in points, never in %, so "+2% of 18%" is impossible to misread.
FX rate EUR→USD 1.086420 1,086420 Always 6 decimals, trailing zeros kept, mono. Shown in the currency control and in every USD Only column header.
Currency code EUR EUR Three-letter codes live in column headers, filters and badges only — never glued to an amount.
Date 12 Mar 2026 12 mar 2026 Day, abbreviated month, 4-digit year. No slashes, so 03/12 is never ambiguous.
Multi-currency totals
Original amounts are never added as they are. A single figure exists only in the Total column, after conversion. Elsewhere: stacked, one line per currency, largest first.
€ 74,215.40
$ 31,980.00
£ 1,240.00
IT layout check
Labels are budgeted at +35%. Column headers wrap to two lines rather than truncate; nav items and buttons never truncate.
Gross Operating Profit
Utile operativo lordo
Expenses by Cost Center
Spese per centro di costo
One screen, one language
Every label comes from the dictionary, including category, item and cost-centre names, which carry an en and an it field. Data with no translation falls back to the stored name and is flagged in Setup, not mixed silently into the page.

06 — Base components

Each one maps to a shadcn/ui primitive: Card, Table, Select, Tabs, ToggleGroup, Badge, Skeleton, Alert, AlertDialog, Button.

Revenue
EUR
€ 74.2k
+8.3%vs Feb
+21.7%vs Mar 25
Profit Net
EUR
−€ 4.1k
−132.4%vs Feb
n/avs Mar 25
Loading — Skeleton
Same box, same height as the loaded card. No spinners, no layout shift.
Financial table
Item EUR USD % Sales
(+) Sales 74,215.40 31,980.00 100.0%
PayPal sales 41,208.00 12,400.00 55.5%
Stripe sales 34,890.40 19,580.00 47.0%
Refunds & disputes −1,883.00 — −2.5%
= Gross Direct Profit 38,402.10 16,110.00 51.7%
Row height 38px. Sticky header and sticky first column. Numeric cells mono, right-aligned, no vertical rules.
Global controls
Month
Quarter
Year
Mar 2026 ▾
All companies ▾
Total in
EUR
USD
FX Mar 2026 · 1.086420
Currencies — Cash Flow and Consolidated
EUR · USD
Always shown: the original amounts of each currency, never converted.
Total
EUR + USD, converted at the month's rate into the currency picked in Total in. The rate sits in the column header.
Yearly
Monthly totals only by default. Show EUR / USD adds the two original lines inside every cell.
Check
Hover any total for its calculation. Show conversion adds the converted column for this session only.
Badges & chips
Income
Expense
Unmapped
EUR
Intra-group
Bank: Wise EUR ✕
Category: Advertising ✕
Clear all
◻
No transactions in Mar 2026
Import a CSV or add a transaction to start the month.
Import CSV
Add transaction
Error state
Couldn't load the P&L
The request timed out after 30s. Nothing was changed.
Retry
Copy error id
req_8f21c04a
Delete 12 transactions?
They will be removed from every P&L and from the consolidated view. This can't be undone.
PeriodMar 2026
Net amount€ 14,208.00
Cancel
Delete