> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sesameterminal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Components

# Components

Sizes are desktop (fine pointer) / mobile (coarse pointer). Every interactive element is
a real `<button>` or `<a>` with an accessible name. Motion for each component is in
[motion.md](motion.md); tokens are in [tokens.md](tokens.md).

**Common states.** These apply to every interactive component unless its section says
otherwise.

| State | Treatment |
| - | - |
| default | As specified per component |
| hover | Fine pointer only (`@media (hover: hover)`). Background steps up one surface (`--accent`) or the tint becomes solid |
| active (pressed) | `transform: scale(0.98)`. The action fires on press; see [interactions.md](interactions.md#click-drag-and-long-press) |
| focus-visible | 2 px `--ring` outline, 1 px offset. Keyboard focus only; mouse focus shows nothing |
| disabled | `opacity: 0.5`, `cursor: not-allowed`, `aria-disabled="true"`, tooltip giving the reason. Keeps its size and position |
| loading | Label stays and a 14 px spinner replaces the leading icon. Width does not change |
| stale | Data-bearing components only: `--muted` background, `--muted-foreground` text, no tint, no flash, clicks do nothing. The panel header shows the age |

## Button

| Size | Height | Padding x | Text | Radius | Use |
| - | - | - | - | - | - |
| default | `--control-h`: 32 desktop / 44 touch | 12 | label (12/16) or body (14/20) bold | `--radius-md` | Every button that shares a row with other controls |
| lg | 44 | 16 | body bold | `--radius-md` | Mobile primary actions, login |
| icon | `--control-h` square | 0 | 16 px icon (20 on touch) | `--radius-md` | Close, pin, bell, overflow, external link |

Controls in one row share one height (`CLAUDE.md` "Layout consistency"); there are no
28 or 36 px buttons beside 32 px ones. Where a row is shorter than 44 px on touch (the
28 px ladder rows on a fine pointer only), buttons grow an invisible hit area (a
`::before` with `inset: -Npx`) up to 44 × 44. Hit areas never overlap a neighbour's;
where rows are too tight, use the mobile layout from the screen spec.

| Variant | Rest | Hover | Notes |
| - | - | - | - |
| primary | `--primary` bg, `--primary-foreground` text | 90% bg opacity | Log in, Save. Never a trade action |
| secondary | `--secondary` bg | `--accent` bg | Common actions |
| outline | transparent, 1 px `--border` | `--accent` bg | Filters, toggles |
| ghost | transparent | `--accent` bg | Toolbar icons |
| destructive | `--destructive` bg, `--destructive-foreground` text | 90% bg opacity | Cancel all only |
| destructive-ghost | transparent, `--destructive` text, 1 px border `--destructive` at 40% | `--destructive` bg, `--destructive-foreground` text | Cancel (N) in ladder headers, the portfolio's `[x] N`, row cancel |
| buy / sell | See [price button](#price-button) | | Trade buttons that show a price |

## Price button

The one-click trade control. It shows the limit price it sends. Used on the board,
watchlist, market view and portfolio (Exit, Join, Add).

| | Desktop | Mobile |
| - | - | - |
| Size | 64 × 32 in a 36 px row | 76 × 44 |
| Content | Price only, numeric-price (15/20 bold): `42¢` | Two lines: caption `Buy` / `Sell` (sentence case, no letter spacing), then price |
| Label | Column header (`Sell` `Buy`) above the column | In the button |
| Radius | `--radius-md` | `--radius-md` |
| Accessible name | `Buy Lakers at 42 cents, stake $25` | same |

Colour follows the side of the order it sends: **Buy** = `--bid` text on `--bid-muted`,
showing the best ask. **Sell** = `--ask` text on `--ask-muted`, showing the best bid.

Layout rules (`CLAUDE.md` "Layout consistency"):

* **Nothing is clipped.** The button is `shrink-0` and its width fits the widest price on
  the market's tick (`99.9¢` on a 0.001 tick) at numeric-price size. A price never
  truncates and never shrinks its font; Exit, Join and Add labels (`Exit 39¢`) reserve
  their widest value the same way.
* **Centred means optically centred.** The price is centred on its own text. The
  reduced-motion direction marker, the sending bar and any other marker that comes and
  goes are positioned absolutely (`position: absolute`, outside the text box) and never
  take width from the price, so `42¢` sits in the same place with or without them, within
  1 px of the button's centre. On mobile the caption and the price are each centred on
  their own text.
* **One gap per group.** Price buttons that form a grid (the mobile Sell/Buy pairs of a
  board card, the portfolio's Exit/Join/Add row) use one gap from the spacing scale, the
  same across and down: 4 px on mobile.

| State | Treatment |
| - | - |
| default, armed | Tint bg, side-colour text |
| hover, armed | Solid `--bid` / `--ask` bg, `-foreground` text. [Cursor tag](#cursor-tag) shows exactly what a click sends |
| active, armed | Solid fill + `scale(0.98)`. **The order is sent on pointerdown** (mouse) or tap (touch) |
| sending | Everything above, plus a 2 px bar in `currentColor` along the inner bottom edge until the server acks. The button stays enabled: another click sends another order (except the double-click guard in interactions.md) |
| focus-visible | 2 px `--ring` outline |
| Safe | Same colours as armed, so the market still reads normally. No hover fill. Cursor tag reads `Safe: arm to trade`. Click does nothing except give the Safe/Armed switch a one-off ring highlight |
| trading off / geoblocked | Tint removed, `--muted-foreground` text, `cursor: not-allowed`. Tooltip `Trading off` / `Geoblocked (US)` |
| sell, no position | Tint removed, `--muted-foreground` text, disabled. Tooltip `No position to sell`. The price stays readable |
| empty side | Blank, tint removed, disabled, tooltip `No bids` / `No asks`. Same size |
| stale | `--muted` bg, `--muted-foreground` text, no flash, disabled. Tooltip `Stale 6s` |
| loading (cold) | Skeleton of the same size |
| price changes | Value swaps instantly, then [flash](motion.md#price-flash) |

## Cancel all

Cancels every open order on every venue. One click, no confirmation, always reachable.
It is the one trading control with icon and text.

| | Desktop top bar | Mobile header |
| - | - | - |
| Size | min 132 × `--control-h` | 64 × 32 visual, 44 × 44 hit |
| Content | `[x] Cancel all` + count `7` | `[x] 7` |
| Tooltip | `Cancel all 7 open orders (Ctrl+Shift+X)` | none; accessible name `Cancel all 7 open orders` |

| State | Treatment |
| - | - |
| open orders > 0 | `destructive` variant (solid) |
| no open orders | `outline` variant, `--muted-foreground` text, disabled, tooltip `No open orders` |
| hover / active | 90% bg / `scale(0.98)`, fires on pointerdown |
| loading | Spinner and `Cancelling…`. Stays enabled: a second click is harmless (cancels are idempotent) |
| result | Toast: `Cancelled 7 orders`, or `Cancelled 5 · 2 in delay window` (see interactions.md) |
| trading off, geoblocked, stale | **Stays enabled.** Cancelling must work when placing does not; the backend decides |

## Safe/Armed switch

A two-segment toggle. The state lives on the server and is checked by `engine/risk`. The
switch shows the server state; it never changes on its own before the server confirms.

```
 desktop 120×32                         mobile 96×32 (44 hit)
 ┌────────────┬───────────┐             ┌──────┬───────┐
 │    Safe    │   Armed   │  (Safe)     │ Safe │ Armed │
 └────────────┴───────────┘             └──────┴───────┘
 ┌────────────┬───────────┐
 │    Safe    │   Armed   │  (Armed: right segment --warning fill, --warning-foreground text)
 └────────────┴───────────┘
```

Text only: Arm/Safe change state, so the segments carry words and no icons.

| State | Treatment |
| - | - |
| Safe | Left segment `--secondary` bg, `--foreground` text. Right segment transparent, `--muted-foreground` |
| Armed | Right segment `--warning` bg, `--warning-foreground` text. Also a 2 px `--warning` line along the top edge of the top bar (desktop) or the bottom edge of the mobile header, so Armed is visible from any screen |
| hover | Inactive segment gets `--accent` bg |
| pending | Spinner in the clicked segment; the old state stays drawn until the server replies. On failure: toast with the error; state unchanged |
| focus-visible | Ring around the whole switch. Space or Enter toggles |
| disabled (trading off / geoblocked) | Both segments muted, single label `Trading off`. Tooltip gives the reason from `/health` |

There is no hotkey to arm; arming is always a deliberate click. Cancel all does not
change the switch.

## Stake selector

The active stake (pUSD) used by every one-click buy. Global for the session.

* **Desktop:** segmented control in the top bar. One segment per preset from settings
  (1–6), 40 × 32 each, numeric (14/20): `10 25 50 100`. The active segment is
  `--primary` bg / `--primary-foreground` text. Label `Stake $` sits left of it.
* **Mobile:** the stake bar: a full-width 44 px row pinned above the tab bar on Live and
  Watchlist, and in the ladder sheet footer. Segments are 44 px tall and share the width
  equally.
* Hotkeys `1`–`6` pick a preset in the focused panel (armed only).
* Disabled segments: none. Stake selection works in Safe.

## Status pill

24 px tall, `--radius-sm`, label text (12/16 semibold), 8 px padding. The top bar shows
the worst current state; clicking opens a popover with one row per feed (market WS,
user WS, sports WS, browser link, geoblock last check).

| State | Look | Text |
| - | - | - |
| live | Transparent, 1 px `--border`, 6 px `--bid` dot, `--muted-foreground` text | `Live` |
| reconnecting | `--warning` fill, spinner | `Reconnecting` |
| stale | `--warning` fill | `Stale 6s` (age ticks each second, in a slot that fits `Stale 59m`) |
| cancel-only | `--warning` fill | `Cancel-only` (venue 503) |
| disconnected | `--destructive` fill | `Disconnected` (browser lost the backend WS) |
| geoblocked | `--destructive` fill | `Geoblocked · US` |
| trading off | `--secondary` fill, `--muted-foreground` text | `Trading off` |

Worst-first order: disconnected, geoblocked, trading off, cancel-only, stale,
reconnecting, live.

Panels showing data from one feed carry a smaller copy (20 px) of the stale pill in
their header while that feed is stale.

## Price cell (with flash)

A non-button price or value in tables and the ladder (`numeric` or `numeric-dense`).

* Right-aligned, fixed width per column, `tabular-nums`.
* Structure: `position: relative` cell, a `::after` flash layer (`inset: 0`, `opacity: 0`,
  `pointer-events: none`) under the text.
* On a change of the displayed value: the value swaps instantly, and the flash layer
  gets `--flash-up` (value rose) or `--flash-down` (value fell) and runs the flash (see
  motion.md). Sizes and depth do not flash; only prices and P\&L columns do.
* Stale: no flash, `--muted-foreground` text.
* Reduced motion: no flash; a static lucide `[arrow-up]` (`--bid`) or `[arrow-down]`
  (`--ask`) after the value, cleared on the next change or after 5 s. It is positioned
  absolutely outside the value's text box, so the value never moves when it appears.
* Nothing is clipped: the cell reserves the widest value of its column and never
  truncates; beyond it the value switches to compact form.

## Order chip

Your own order, drawn on the ladder level it rests at and in the Status column of the
orders table.

| | Ladder (fine) | Ladder (coarse) | Orders table |
| - | - | - | - |
| Height | 20 in a 28 row | 32 in a 44 row | 24 |
| Content | Size in at most four characters (`25`, `12k`), beside the level's size, never over it; two or more orders at a level show a second edge behind the chip, with the count in the tooltip | Size, with `×2` as text when orders share a level (wide cells) | Status text |
| Font | numeric-dense | numeric-dense coarse | label |
| Radius | `--radius-sm` | `--radius-sm` | `--radius-sm` |

Side colour: buy chips use `--bid` text and border on `--bid-muted`; sell chips use `--ask`.

Layout rules (`CLAUDE.md` "Layout consistency"):

* **Nothing is clipped.** A chip is `shrink-0` and never truncates its size or count. Its
  width fits the widest value it can show in its slot (`12.3k`, `12/25`, `×2`); a larger
  size switches to compact form (`1.2k`) with the exact size in the tooltip. In the ladder
  the chip sits at the outer edge of the Bid or Ask cell and the depth size gives way
  (it moves under the chip's opposite side), never the chip.
* **Centred means optically centred.** The size is centred on its own text. State
  markers that come and go (the `[check]` on a fill, `[circle-alert]` on a reject,
  `[circle-help]` on an unknown order, the countdown ring) are positioned absolutely at
  the chip's inner left edge, inside padding the chip always has, so the size does not
  move when a marker appears or leaves.
* A chip and the other controls in its row (steppers, cancel button in the orders table)
  use one height: the table's `--control-h`, or the ladder's chip height below.

| State | Ladder chip | Orders table text | Interactions |
| - | - | - | - |
| sending | Dashed 1 px border, side colour | `Sending` | Not draggable until acked |
| placed (open) | Solid 1 px border, tint bg | `Open` | Drag to reprice, right-click to cancel |
| delayed | `--warning` bg, `--warning-foreground` text, 12 px [countdown ring](#countdown-ring) + seconds | `Delayed 3s` | Not draggable. Cancel disabled; right-click shows tooltip `In delay window (3s)` |
| partially filled | Solid border, text `12/25`, 2 px fill bar along the bottom (`transform: scaleX(filled/size)`) | `Partial 12/25` | As placed |
| filled | Solid `--bid`/`--ask` bg, `-foreground` text, lucide `[check]`. Shown 1.5 s, then removed | `Filled` | None |
| cancelling | Text struck through, 60% opacity | `Cancelling` | None |
| cancelled | Removed from the ladder | `Cancelled`, `--muted-foreground` | None |
| rejected | Transparent, 1 px `--destructive` border, `--destructive` text, lucide `[circle-alert]`. Shown 3 s at the level, then removed | `Rejected` + reason on hover | None; the `order_rejected` notification carries the reason |
| moving (reprice) | Dashed chip at the old level, sending chip at the new level | `Moving → 44¢` | None |
| unknown (reconciling) | Dashed border, lucide `[circle-help]`, 60% opacity | `Checking…` | None; resolves to placed or removed after the backend reconciles |

## Countdown ring

12 px (ladder) or 14 px (tables, board) circle in `--warning-foreground` on the warning
chip, emptying clockwise over the real delay length. Next to it, whole seconds left.
Built from two half-discs rotated with `transform` so only `transform` animates. When the
backend sends no end time, the ring is hidden and the chip reads `Delayed`.

## Cursor tag

A small label that follows the mouse over any trade target and states exactly what a
click sends. Fine pointer only; mobile uses the stake bar instead.

* 24 px tall, `--popover` bg, 1 px `--border`, `--shadow-md`, label text, offset 12 px
  right and 16 px below the pointer, flipped at viewport edges. Moves with `transform`.
* Appears on the first frame of hover, with no delay and no fade.
* Content:
  * Buy: `Buy $25 → 59.52 sh @ 42¢`
  * Sell: `Sell 59.52 sh @ 44¢ · +$1.19`
  * Drag: `Move buy 25 → 44¢`
  * Safe: `Safe: arm to trade`
  * Stale: `Stale 6s`
  * Sell with no position: `No position to sell`
* Share counts are an estimate. The backend rounds size to the market's minimum and tick
  before signing.

## Tick stepper

`[−] 42¢ [+]` inline editor for an order's price. Buttons are `--control-h` square
(32 desktop, 44 mobile); the price between them is numeric, 48 px wide, centred on its
own text. The two buttons, the price and a following cancel button keep one gap (4 px). Each click moves the
price by one tick and sends a replace (interactions.md). While a replace is in flight,
the price shows the target in `--muted-foreground` with a `→` prefix. `−` is disabled at
the lowest tick and `+` at the highest; both are disabled while the order is delayed.

## Panel

Any focusable region that owns hotkeys: board, each docked ladder, dock tabs, portfolio,
orders.

* Header: 36 px, heading text, `--border` bottom line, actions on the right.
* **Focused:** header title in `--foreground`. A 2 px `--ring` line along the panel's top
  edge (the panel focus indicator) shows only after F6 / Shift+F6, or while Armed with more
  than one panel that takes trading keys visible (interactions.md). Unfocused headers use `--muted-foreground`. Exactly one
  panel is focused at a time.
* Focus moves on pointerdown anywhere in the panel, with F6 / Shift+F6, or when a panel
  opens.

## Position row

36 px table row (portfolio, bottom dock). The full column spec is in
[portfolio.md](screens/portfolio.md).

| State | Treatment |
| - | - |
| default | `--card` bg, 1 px `--border` bottom |
| hover | `--accent` bg |
| selected (keyboard) | `--accent` bg + 2 px `--ring` bar on the left edge |
| expanded | Chevron rotated 90°, ladder area below (motion.md) |
| exiting | Exit button in loading state; other actions stay enabled |
| stale | Price, liquidation value and P\&L cells stale-styled; Exit, Join and Add disabled (they would send stale prices); the cancel `[x] N` stays enabled |
| resolved | Size and outcome kept; value = payout. Actions replaced by status `Won` / `Lost` and a link `Claim on polymarket.com [external-link]` (opens a new tab). No in-app redeem |
| loading | Skeleton row |

## Ladder row

One price level: `--ladder-row-h` tall. Full spec in
[price-ladder.md](screens/price-ladder.md).

| State | Treatment |
| - | - |
| default | `--card` bg |
| in the spread (between best bid and best ask) | `--muted` bg |
| best bid / best ask | Size and price at weight 800 |
| last trade | A small `--foreground` notch (5 × 8 px triangle) at the price cell's right edge, vertically centred and pointing at the price. Not a full-height bar, so it never reads as a text caret |
| hover | Row `--accent` bg; the hovered bid or ask cell takes the solid side fill and the cursor tag shows |
| own order | Order chip in the bid or ask cell |
| drag target | Row outlined 1 px dashed `--ring` |
| stale | Every cell stale-styled; depth bars hidden; clicks disabled |

## Score badge

| Size | Use | Layout |
| - | - | - |
| compact | Board row, portfolio, palette | body-sm: `88–84 · Q3 4:12` with a 6 px state dot on the left |
| large | Market view header | title-size score, body-sm period and clock under it |

Possession: a 4 × 12 bar in `--foreground` next to the team with the ball (compact) or
the word `ball` after the team abbreviation (large: `LAL ball`). Score values change instantly; a changed score gets a
400 ms `--ring` outline (motion.md), never a count-up.

| State | Dot | Text |
| - | - | - |
| pre-game | none | Start time `19:30`, `in 12m` inside the hour, `in 1:41` in the last 10 minutes (interactions.md, "One start format") |
| live | `--bid` | Score, period, clock |
| break | `--muted-foreground` | Score + `HT`, `Break`, `End Q2` |
| final | none | `Final`, winner's score bold |
| postponed / cancelled | none | `Postponed` in `--muted-foreground` |
| stale | `--warning` | Stale-styled text + `stale` |

## Toast

sonner. Desktop: bottom-right, 16 px above the bottom dock, 360 px wide; at most 3
visible, older ones collapse behind and expand on hover. Mobile: at the bottom, above
the stake bar and tab bar, 8 px side margins, one visible at a time, so the header
(Safe/Armed and Cancel all) is never covered.

* 56 px minimum height, `--popover` bg, `--shadow-md`, the overlay radius. A title line
  and a body line (body-sm, muted, up to two lines). A 3 px left stripe only for warning
  and critical; at most one text action and a close `[x]`.
* Hover pauses the timer (desktop). Swipe dismisses (mobile).

There are two sources of toasts, and one event never shows two:

* **Notifications** (fills, rejects, alerts, feed and trading-state events) come only
  from the `notifications` WS topic, with their severity's stripe and duration: info 4 s,
  warning 8 s, critical until closed. Copy, stacking and coalescing are in
  [notifications.md](screens/notifications.md).
* **Results of the user's own action** that is not a notification kind stay local:

| Variant | Stripe | Text example | Duration |
| - | - | - | - |
| cancelled | none | `Cancelled 7 orders` | 3 s |
| undo | none | `Alert deleted · Lakers bid at or above 60¢` + `Undo` | 5 s |
| saved | none | `Risk limits saved` | 3 s |
| error | `--destructive` | `Cancel failed · 503 cancel-only mode` | 8 s |

Sounds and desktop notifications follow the notification rules in
[notifications.md](screens/notifications.md); local toasts play no sound.

## Command palette row

40 px (desktop) / 52 px (mobile).

```
│ [trending-up]  Lakers v Celtics                   88–84 Q3 4:12   42¢ / 44¢ │
│                NBA · Moneyline                                              │
 kind icon (16 px: trending-up market, users team, hash tag, history recent,
 chevron-right command), title body, subtitle body-sm muted, right side numeric muted
```

| State | Treatment |
| - | - |
| default | Transparent |
| selected (arrow keys or hover) | `--accent` bg |
| pinned | `[star]` icon after the title |
| held | Position badge after the title: `120 sh` in `--muted-foreground`, 12 px, weight 600. A share count carries no gain or loss colour |
| live | `--bid` dot on the icon |
| command | `[chevron-right]` kind icon, and a key hint on the right (`Ctrl+Shift+X`) |
| disabled command | 50% opacity, reason as the subtitle (`Trading off`) |

Rows render instantly; they never animate in.

## Empty states

Centred in the panel. 24 px icon in `--muted-foreground`, heading text, one line of
body-sm, and at most one action button (`secondary`). Keep the panel's own height;
do not collapse it.

| Where | Heading | Body | Action |
| - | - | - | - |
| Board, no live games | `No live games` | `Next: NBA · Lakers v Celtics at 19:30` | `Show upcoming` |
| Watchlist | `Nothing pinned` | `Pin a market with Shift+Enter in search, or the pin button on any market` | `Search` |
| Portfolio | `No positions` | `Positions appear here after your first fill` | none |
| Orders | `No open orders` | none | none |
| Alerts | `No alerts` | `Set one with the bell on a ladder price or from a game’s menu` | none (the header has `Set alert…`) |
| Notifications | `No notifications in the last 30 days` | none | none |
| Search, no results | `No matches for "lakrs"` | `Try a team, event or #tag` | none |
| Ladder, no book | `No book` | `Market closed` or `Resolved: Yes` | none |
| Dock tab | `No fills today` etc. | none | none |

## Skeletons

Shown only on a cold load (no cached data). Same geometry as the real content: the same
row heights, column widths and button sizes, so nothing shifts when data arrives.
`--muted` blocks with `--radius-sm`. **Static, no shimmer or pulse** (a pulse would
exceed `--dur-slow`). Text blocks are 60–80% of the column width; numbers are right-aligned
blocks. Replace the whole skeleton in one frame when data arrives.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.