Titandocs

Conventions

The rules that keep 200 components looking like one person wrote them.

Exports & components

  • Named exports only. No default exports for components.
  • memo on every UI component, with an explicit displayName — anonymous memo wrappers make React DevTools unreadable.
  • forwardRef on anything wrapping a DOM element (button, input, …).
  • useCallback for handlers passed to memoized children — otherwise the memo does nothing.
  • Module-level constants for static config (NAV_TABS, NUMBER_FORMAT_OPTIONS), out of the component body for referential stability.
const PairRowInner = forwardRef<HTMLButtonElement, PairRowProps>(({ market, ...props }, ref) => (
  <button ref={ref} data-active={props.active} {...props} />
));

export const PairRow = memo(PairRowInner);
PairRow.displayName = 'PairRow';

Styling

  • Tokens live in src/index.css under @theme. Reference via Tailwind utilities or CSS vars — never hardcode a hex.
  • Component visuals live in CSS (@layer components); React components are thin wrappers that apply the class and forward props.
  • State modifiers go on data-* attributes, never conditional class strings.
// ✅ state is data, CSS reacts to it
<button data-active={isActive} className="pill-tab" />

// ❌ state smeared into a class string
<button className={`pill-tab ${isActive ? 'pill-tab-active' : ''}`} />

The data-* rule is not stylistic. It means a designer can restyle the active state by editing one CSS block, without opening a .tsx file — and it makes the DOM self-describing in tests ([data-active="true"]).

  • cn() (src/lib/cn.ts) joins class names. No clsx, no classnames dependency.

Numbers

  • Always tabular-nums. Prices, sizes, PnL, percentages.
  • Mono for data — live prices, addresses, the order book, timestamps.
  • Green = long / gain, red = short / loss. Never inverted, in any locale.
  • Financial values respect hideValues (masked as •••) so a screen can be shared without leaking position size.

PostHog session replay masks .tabular-nums, .font-mono, and [data-ph-mask]. Rendering a balance without one of those classes leaks it into replay recordings.

Comments

Only when the why is non-obvious. Never restate the code.

// ❌ increments the nonce
nonce += 1;

// ✅ the engine rejects a reused nonce even for an identical payload, so
// this must increment before the retry, not after the response.
nonce += 1;

i18n

All user-facing strings go through useT(). Nine languages live in src/i18n/ (en · fr · es · ko · zh · ru · th · vi · hi).

const t = useT();
<span>{t('order.form.submit')}</span>

A hardcoded English string is a bug, not a shortcut — the topbar, order form, and wallet modal are all fully translated, so a raw string stands out immediately in a non-English locale.

On this page