SabSahi

Phase 1.10 · admin

Bright brand. Calm interface.

Every token and component, read from the live theme rather than copied into this page. Switch the theme to check both — light and dark are equal citizens, not one inverted from the other.

sections
20sections
primitives
19primitives
terms
66terms
gradients
4gradients

§3–§5 · Colour

Five brand colours. Everything else does the structural work.

Values and contrast are measured in the browser, in the theme you are viewing. Ratios are against bg-surface.

Brand

The logo palette. Identical in both themes — never redefined per theme (§3).

brand-blue
brand-deep
brand-teal
brand-lime
brand-coral

Brand as text

Used when a brand colour must carry type. The fills above are 1.95:1 to 3.21:1 on white and cannot (D-072).

brand-blue-ink
brand-teal-ink
brand-lime-ink
brand-coral-ink

Surface

utilities: bg-canvas · bg-surface · bg-subtle · bg-raised

bg-canvas
bg-surface
bg-subtle
bg-raised

Text

utilities: text-ink · text-ink-2 · text-ink-3 — never text-primary, which is shadcn’s and means the brand blue.

text-primary
text-secondary
text-muted
text-on-brand

Ink on a brand fill

text-on-brand is white and belongs only on blue or deep. text-on-warm is the dark ink coral and lime need, and is fixed in both themes because the fills under it are (§3, D-075).

text-on-brand
text-on-warm

Border

utilities: border-line · border-line-strong · border-field. Only border-field clears WCAG 1.4.11’s 3:1 for a control boundary.

border-default
border-strong
border-input

State

utilities: text-success · text-warning · text-error · text-info. Error is semantic, never brand coral (§13).

state-success
state-warning
state-error
state-info

Card tints

utilities: bg-progress · bg-completed · bg-upcoming · bg-information (§15). ⚠️ text-ink-3 is never legible on these — see the note under Card families.

accent-progress
accent-completed
accent-upcoming
accent-information

Button depth

The slab a press-shaped button sinks into (D-075). Behind the fill, so it carries no contrast floor. Blue and coral are theme-fixed; only the neutral one follows the surface.

depth-blue
depth-coral
depth-neutral

§11 · Gradients

Four ramps. Three inks, and one that carries none.

Selected brand moments only — never the default primary button, and never a rainbow of all five brand colours.

Decoration only

Primary brand

--gradient-brand

Onboarding accents, splash artwork, decorative fills

No text. White is 2.38:1 at the teal end, dark ink 2.71:1 at the blue.

Legible end to end

Trust

--gradient-deep

§40 subscription and premium surfaces, sign-in, splash

White, 5.86:1 → 9.80:1. The only ramp white is safe on.

Legible end to end

Celebration

--gradient-celebrate

Moments Engine and completion. Used sparingly

Dark ink, 6.66:1 → 8.13:1.

Legible end to end

Warm attention

--gradient-warm

Reminders, streaks, a nudge that is not an error

Dark ink, 4.95:1 → 8.13:1.

⚠️ A gradient is not a colour, so “it passes contrast” has to be true at both ends and in the middle. White on --gradient-brand is 5.86:1 where it starts and 2.38:1 by the teal — this page shipped exactly that mistake before the gate existed. contrast-check.mjs now measures every stop and the midpoint of each ramp against the ink named above.

§6 · Typography

Fredoka for joy. Nunito Sans for clarity.

Fredoka carries emotion; it is banned from body copy, forms, tables, money and Vault UI. Nunito Sans does everything else.

Display — Fredoka

Greetings, onboarding, Moments, empty states

Aaj kaafi sahi thaDisplay XL · Fredoka 600
Aaj kaafi sahi thaDisplay · Fredoka 600
Aaj kaafi sahi thaH1 Expressive · Fredoka 600
Aaj kaafi sahi thaH2 Expressive · Fredoka 600
Aaj kaafi sahi thaFun microcopy · Fredoka 500/600

Functional — Nunito Sans

Body, forms, tables, money, settings

You paid ₹18,000 to AmazonUI H1 · Nunito Sans 700/800
You paid ₹18,000 to AmazonUI H2 · Nunito Sans 700
You paid ₹18,000 to AmazonUI H3 · Nunito Sans 700
You paid ₹18,000 to AmazonBody Large · Nunito Sans 400/500
You paid ₹18,000 to AmazonBody · Nunito Sans 400/500
You paid ₹18,000 to AmazonSmall · Nunito Sans 500
You paid ₹18,000 to AmazonLabel · Nunito Sans 600/700
You paid ₹18,000 to AmazonCaption · Nunito Sans 500/600
You paid ₹18,000 to AmazonButton · Nunito Sans 700/800

Tabular numerals

₹18,000.00 · ₹1,05,250.50 · ₹99.99

Money uses data-numeric, so digits hold a column and a value ticking from 9 to 10 does not shift the row.

§7 · Shape

Rounded and soft, not one corner everywhere.

A deliberate scale. The button step is a squircle (D-075).

8pxcompact controls
12pxfields, chips
16pxsmall cards
20pxgrouped panels
22pxbuttons (squircle)
24pxfeature cards
28pxhero / Today cards
999pxpills

§8 · Spacing

Everything lands on a 4px grid.

Card padding 16–24, major groups 24–32, page sections 40–64.

4
8
12
16
20
24
32
40
48
64
80
96

§27 · Motion

Motion explains. It never decorates.

All of it is disabled under prefers-reduced-motion — with one deliberate exemption.

  • --duration-controlbutton, hover
  • --duration-dropdowndropdown
  • --duration-cardcard reveal
  • --duration-sheetsheet, drawer
  • --duration-pagepage transition
  • --duration-celebratecelebration — the ceiling, not the target

⚠️ The spinner is exempt, keyed on data-slot="spinner". The blanket rule does not reduce a spinner, it freezes it — one iteration at 0.01ms leaves a still icon where the only sign of work used to be.

§10 · Elevation

Light lifts with shadow. Dark lifts with edges.

Switch the theme — two of these three go flat on purpose.

--shadow-card
--shadow-raised
--shadow-floating

§12 · Iconography

One family, in a pale container.

Rounded line icons, ~1.75–2px stroke, filled only for selected or celebratory states.

Informationblue on pale blue
Progressteal on pale teal
Completedlime on pale lime
Remindercoral on pale coral

⚠️ The icon takes the -ink variant, not the brand fill. Brand teal on pale teal is 2.1:1 and disappears. A destructive icon uses the semantic error, never coral.

§7 · Organic accents

Personality at the edges, never over the data.

Soft blobs, curved underlines, radial marks from the logo, pill chips. Used selectively.

Soft blob
sortedCurved underline
Radial mark
₹ ExpensePill chip

⚠️ §7 also says where these may not go: never distorting a form control, never behind dense data, and never on every card. The radial mark is drawn from the logo’s capsule rays — it is not the logo, and must never be used as one.

§15 · Cards

Vary the composition, not just the colour.

Do NOT build Today as six identical KPI cards. Vary by content: list, progress, reminder, money snapshot, moment.

Standard

Neutral surface, minimal border

Active

Blue tint and border — the selected one

Progress

Pale teal — active life areas

Completed

Pale lime — achievements

Upcoming

Pale coral — warm attention

Informational

Pale blue — context

Good morning, Ayesha!

Three things deserve your attention today.

Feature / Today hero — 28px radius, richer composition

⚠️ The hero uses --gradient-deep, not --gradient-brand. White is legible end to end on the first and fails at the teal end of the second.
⚠️ text-ink-3 is never legible on a card tint — 4.33:1 on bg-information in light, 4.04:1 on the tints in dark. Secondary text on a tint is text-ink-2, and the design-system check enforces it.

§13, §37 · Buttons

Functional first. Cheerful second.

Blue is the default interactive colour. Coral is the warm brand action, never a generic submit and never an error.

Squircle — the default shape

Superellipse corners plus a hairline top sheen. corner-shape is Chrome 139+; elsewhere the corner falls back to a circular 22px, which at 44px tall is a capsule — the logo's own geometry, so the fallback is on brand rather than broken.

Press — the second shape

A 4px slab of the fill's own darker brand tone, which the button sinks into on :active. §3 already assigns deep blue to the pressed state. Ghost and link have no body to press, so they opt out.

Sizes

Default is 44px and large is 48px — §13's range, and §33's touch target. xs and sm are for dense admin surfaces only.

With an icon

§12 — never rely on the icon alone where the meaning could be read two ways. An icon-only button still needs an accessible name.

§37 — every state, not just the default

Four of these need you: put the pointer on the first one, hold it down, and tab to it. The rest are rendered as they ship.

default
At rest
hover
Pointer over it — never the only way to reach anything
active
Being pressed. The squircle dips, the press slab collapses
focus-visible
Reached by keyboard. 2px brand-blue ring, always visible
selected
Chosen, and stays chosen
disabled
Reduced emphasis, still readable
loading
Working. The label stays, so the width does not jump
error
Semantic error plus a message in words
success
Semantic success plus a message in words

⚠️ The loading button keeps its label and its width. Swapping the text for a bare spinner makes the row reflow under the cursor, so the next click lands on whatever moved into that spot.

§14 · Form controls

A place to type, not a thing to press.

Minimum 44px. Labels stay visible, required is explicit, and an error pairs the semantic colour with a message in words.

Money is stored in paise and shown in rupees. Digits are tabular so columns line up.

That does not look like a 16-digit card number. Check the last four digits.

Optional. One or two lines is plenty.

Who does this belong to?

⚠️ “Shared with Meera” is a relationship, not a financial treatment. §3.5 — relationship never decides how money is split.

Include in

A disabled control still has to be readable — §13 and §37.

A switch applies the moment it moves. Anything needing a Save button is a checkbox.

§5.4, §20 · Help

Four levels, all of them reachable.

Tooltip, popover and sheet. Every trigger works on click, tap and keyboard.

⚠️ A tooltip may only ever add to a focusable control, never be the sole route to the information — a touchscreen has no hover at all, which is most of India.

§31, §32 · Loading

Say what is happening, and how far along.

Skeleton for a content area, spinner for a short action, progress where the share is known.

Progress — the share is known

Important things today
x
Importing your statement
x

The second one is indeterminate — value={null}. It says work is happening without inventing a percentage nobody measured.

Spinner — a short action

Skeleton — a content area (§31)

⚠️ A skeleton must be roughly the shape of what replaces it. One that is the wrong size makes the page jump when the content lands, which is worse than an empty box.

Toast — a result needing no action (§32)

§32 — a toast is for something the person does not have to act on. Anything they must acknowledge is inline or a dialog, and destructive confirmation is never a toast.

Inline result (§32)

Saved to August. Your reports have been updated.

Colour is never the only signal — the tick and the sentence say it too (§22, §33).

§22 · Notifications

Semantic colour means system. Brand colour means category.

Never colour alone: every one of these carries an icon and says the same thing in words.

⚠️ security is deliberately colourless. §39 wants trust surfaces calm and undecorated — a red border around a factual sign-in notice reads as an accusation. Celebration is the only one allowed a Fredoka title, and only a short one.

§21 · Dense data

Money stays calm, and lines up.

Nunito Sans only, neutral styling, subtle row hover, numbers aligned, status chips used sparingly.

DateWhoKindAmountStatus
24 AugAmazonShopping18,000.00Confirmed
23 AugMeera — rent shareSettlement12,500.00Confirmed
22 AugHDFC statement row 41Uncategorised1,05,250.50Draft
21 AugLIC premiumPolicy9,412.00Due

The header is sticky and the amount column is data-numeric and right aligned, so ₹1,05,250.50 and ₹99.99 line up on the decimal. ⚠️ Scroll it — a sticky header inside a short card is worse than none, so it is opt-in per table, not a default.

§19 · Empty states

Say what goes here. Offer one way in.

Fredoka for the short title only. Never fake data, and never more than one action.

Nothing here yet

Money you record will show up here, grouped by month.

Nothing overdue. Nice.

You have no reminders due today.

⚠️ No button on purpose. This is good news, and an action here invents work.

§21 · Chips

Small pills, used sparingly.

A status chip earns its place by being rare. The composer's chips are real form controls, not these.

defaultsecondarycheerfulsuccesswarninginfodestructiveoutline

A separator above. §21 asks for restraint with rules in tables — prefer spacing to lines.

terminology.md §7 · Registry

Every word the product says, all 66 of them.

Rendered from the registry so drift is visible. The UI label is what a person reads; the internal name is never shown to them.

termIdUI labelInternalLevel 4?
allocationSet aside for someoneMoney handed over for household, personal or savings use, with nothing expected back.household_allocationpopover only
base_currencyYour main currencyThe currency your totals are shown in.base_currencypopover only
borrowerWho did you lend the money to?The person who took the money and will return it.borrower_person_idpopover only
capital_contributionMoney you put into your businessIt moved from you to your business. It is not spending.capital_contributionpopover only
categoryWhat was it for?Groceries, education, fuel. Separate from what the money *meant*.category_idpopover only
circleA group of peopleA named set of people you can greet, invite or split with.circlepopover only
claimSent to be repaidThings you paid for at work, sent together so they come back in one payment.claimpopover only
classificationHow protected is this?Controls what can be shared, exported, searched or read by AI.classificationpopover only
closenessHow close are you?Private to you. It decides who to show first, and nothing about money.closenesspopover only
coverage_episodeOne period of coverOne provider, one continuous period. A new provider starts a new one.policy_coverage_episodepopover only
deceasedNo longer with usOccasions become remembrances. Celebratory reminders stop.deceased_onpopover only
dependentSomeone you look afterA child or family member whose records you manage.dependent_ofpopover only
dishSomething they cook wellA dish, who makes it, and what it looked like the last time.dishpopover only
draftNot confirmed yetImported or suggested. It affects nothing until you confirm it.status = draftpopover only
entitlementWhat your plan includesThe features and limits available to you.entitlementpopover only
family_supportGiven, not expected backMoney given to family with no repayment expected. Not a loan.family_supportpopover only
financial_contextWhose money is this?Your own, a job, or your business. Kept apart so one total never hides inside another.context_idpopover only
funding_personWho puts money in?The person paying into the investment, who may not own it.funding_person_idpopover only
fx_rateExchange rate usedThe rate on the day. Saved so history never changes.ratepopover only
gift_givenSomething you gaveWhat you gave, to whom, and when. The amount comes from the money you recorded, if you recorded any.giftpopover only
gift_receivedSomething you were givenA gift someone gave you. It is never counted as income.giftpopover only
guardianWho manages this?The person responsible for a dependent's records or accounts.guardian_ofpopover only
householdYour spaceEverything in SabSahi belongs to your household.household_idpopover only
how_it_feltHow did it feel?One word for the day. It is not a score out of five.feltpopover only
income_episodeA change in what you earnEvery revision is kept with its dates. The earlier one is never written over.income_episodepopover only
income_sourceWhere the money comes fromA job, a business, rent, or anything else that pays you regularly.income_sourcepopover only
insuredWho is covered?Everyone the policy protects. May differ from the holder.insured_person_idspopover only
intentWhat does this money mean?Whether this was spending, a loan, a transfer, savings or a gift.intentpopover only
lapsedCover stoppedThe policy ended without renewal. The gap is kept on record.statuspopover only
lenderWho did you borrow the money from?The person who gave you the money and expects it back.lender_person_idwritten
memorySomething worth keepingA day that happened, with the people, the place and your own words.memorypopover only
memory_kindWhat kind of memory?A celebration, a milestone, an achievement, a first time, or an ordinary good day.kindpopover only
momentWorth your attentionSomething SabSahi thinks you want to know about today.momentpopover only
mute_memoriesStop showing theseHide a person, a place or a set of dates from memories. No reason needed, and you are never asked again.resurface_mutepopover only
nomineeWho receives the benefit?The person nominated to receive the payout.nominee_person_idspopover only
organisation_personA company or organisationAn employer, a client or your society. It can owe you money like anyone else.kindpopover only
outstandingStill to be returnedWhat is left after repayments so far.outstanding_minorpopover only
own_expenseEveryone pays for themselvesNobody owes anybody anything, and no balances are created.cost_modepopover only
owner_drawingMoney you took out of your businessIt moved from your business to you. It is not income.owner_drawingpopover only
payablesMoney you need to payAmounts you owe, across everything.obligation (debtor = self)popover only
personPersonAnyone in your life. They do not need a SabSahi account.personpopover only
personal_allowanceSpending money givenGiven for personal use. SabSahi does not assume it was all spent.personal_allowancepopover only
planSomething you are planningAn idea for doing something together, before it is certain.planpopover only
plan_linkA link you can shareAnyone with the link sees the plan and nothing else. Never your money, never anyone's phone number.share_grantpopover only
plan_replyAre they coming?Going, not going, maybe, or no reply yet.statuspopover only
policyholderWhose policy is it?The person the policy belongs to.policyholder_person_idpopover only
portedMoved to another companyCover continued with a new provider, keeping the old history.statuspopover only
premium_payerWho pays for this policy?The person actually paying. Often not the policyholder.payer_person_idpopover only
principalOriginal amount borrowedThe starting amount, before any repayments.principal_minorpopover only
receivablesMoney others need to pay youAmounts people owe you, across everything.obligation (creditor = self)popover only
reimbursableYou paid, they will return itMoney you paid for someone else and expect back. Not counted as your spending.reimbursable_expensepopover only
relationshipHow are they related?A social relationship only. It never decides how money is treated.relationshippopover only
resurfacedFrom around this daySabSahi noticed a memory from this time in an earlier year.momentpopover only
savings_allocationMoney set asideKept, not spent. Shows under "set aside", never under spending.savings_allocationpopover only
self_personYouYour own record. Used when you are part of a split.is_selfpopover only
settlementClearing what was owedPaying off a balance. It is not a new expense or income.settlement_paid / _receivedpopover only
share_grantSharing thisWho can see it, for how long, and whether they can download it.share_grantpopover only
sipMonthly investmentA fixed amount invested on a schedule.investment + schedulepopover only
sponsoredYou are paying, no return expectedTheir share is on you, and no balance is created.split typepopover only
stored_valueMoney loaded in advanceA metro card or a wallet. Counted when you load it, unless you choose to record each journey.accountpopover only
storyA slideshow of your memoriesPhotos put in order with words, ready to watch. You can change it whenever you like.storypopover only
tracking_boundaryKeep tracking this money?Choose whether SabSahi follows what happens to this money afterwards, or stops here.tracking_boundarypopover only
treatmentWhat does this money mean for this person?Personal or shared for your own portion. Reimbursable, gift or family support for someone else's. Who the person is never decides it.movement_line.intentpopover only
treatment_giftGiven for them, nothing expected backCounted as your spending, as a gift. It never creates a balance.giftpopover only
treatment_personalYour own portionCounted as your spending. Nobody owes anything for it.personal_expensepopover only
treatment_sharedYour share of something splitOnly your share counts as your spending. The other shares belong to the people who owe them or were given them.shared_expensepopover only

Fredoka for joy. Nunito Sans for clarity.

Bright brand colours, calm foundations, varied card composition, helpful explanations and small delightful moments — a consumer-friendly personality without compromising trust. Everything here is read from the live theme, so when it drifts, this page is where you see it first.