Compliance checklist
Run this before opening a PR that touches any surface.
If a line fails, the fix is almost always "use the token" rather than "adjust the value".
Tokens
Search your diff for # followed by 3/6 hex digits in a .tsx file. Every match is a bug —
including "just this one time for a gradient". Gradients get tokens too (see the chain colours).
5 / 8 / 11 / 18 / 22 / 9999. No intermediates. A rounded-[14px] in a diff is a rejection.
text-[var(--t2)], not text-neutral-400. The Tailwind palette does not flip with the theme;
the ink ramp does.
Colour semantics
- Green appears only for gain / long / buy / success / up.
- Red appears only for loss / short / sell / error / down.
-
--warnis used for risk — liquidation price, capacity, alerts — not red. - No green or red on a non-financial element.
- Badges use the soft background with the solid text colour, never solid-on-solid.
- Variable intensity uses
color-mix, not a hand-picked second shade.
Typography
- Every number carries
tabular-nums. - Live prices, addresses, order book, and timestamps are mono.
- Column headers are uppercase,
tracking-[0.06–0.1em],--t3. - Large text has tighter tracking, not looser.
- Money values split integer (
--t1) from decimals (--t2).
Surfaces & elevation
- Interactive controls sit on
--surface-3; their active state drops to--surface. - In-page cards have no shadow.
- Only floating layers (popover, modal, context menu, toast) cast a shadow.
- Floating layers combine translucency +
backdrop-blur(28px) saturate(1.6).
Motion
- All animation is inside
@media (prefers-reduced-motion: no-preference). - Page entrances translate only — never animate opacity.
- Interactive elements press with
scale(.96). - Durations sit in 120–350 ms.
- Springs use
cubic-bezier(.34,1.56,.64,1); general easing usescubic-bezier(.2,.7,.3,1).
Component hygiene
- Named export,
memo, explicitdisplayName. -
forwardRefon anything wrapping a DOM element. - State lives on
data-*, not a conditional class string. - Visuals live in CSS; the component is a thin wrapper.
- Static config is a module-level constant.
-
cn()for class joining — noclsx.
Accessibility
- Focus is visible and uses
--focus-ring(the only blue in the chrome). - The active nav item carries
aria-current="page". - Modals dismiss on Escape and overlay click.
- Contrast:
--t3is0.55dark /0.62light. Do not restore the old0.4. - Colour is never the only signal — pair it with a label, sign, or icon.
Privacy
- Financial values respect
hideValues(masked as•••). - Anything sensitive carries
.tabular-nums,.font-mono, or[data-ph-mask]so PostHog session replay masks it.
The masking rule is easy to break by accident: rendering a balance in a plain <span> without a
numeric class leaks it into replay recordings. If a value is money, it gets one of those three
classes — the mask is a side effect of following the typography rule, which is why the two rules
are worth keeping coupled.
i18n
- Every user-facing string goes through
useT(). - No hardcoded English in a component.
- Number formatting respects the
numberFormatpreference, not a hardcoded locale.