Skip to content
Components

Components

38 Cryonix components, each shown live with its code. Import from @/components/ui, or use the .cx- classes directly.

Actions

Button

Pill-shaped actions. One solid accent button per view; everything else ghost or quiet.

From the siteButton
Code
<Button arrow>Get started <ArrowIcon /></Button>
<Button variant="ghost">Secondary</Button>
<Button variant="quiet">Quiet</Button>
<Button variant="danger">Remove card</Button>

Labels

Badge

The eyebrow pill that sits above a section heading and names the topic in two or three words.

From the siteBadge
Instant transfersMulti-currency walletNow in 34 countries
Code
<Badge className="cx-lit" icon={<SparkleIcon />}>Instant transfers</Badge>
<Badge className="cx-lit">Now in 34 countries</Badge>

Chip

Small tonal pills for status, price movement and unread counts, plus the keyboard-key token.

ExtensionChip, DeltaChip, Count
StandardReceivedFailedPending reviewProVerifiedProcessing
Code
<Chip>Standard</Chip>
<Chip tone="gain">Received</Chip>
<Chip tone="loss">Failed</Chip>
<Chip tone="warning">Pending review</Chip>
<Chip tone="brand">Pro</Chip>
<Chip tone="gain" dot>Verified</Chip>

Avatar

A round portrait or initials token for people in transfer lists, payees and shared splits.

ExtensionAvatar
Code
<Avatar size="sm" name="Tobi Adeyemi" />
<Avatar name="Tobi Adeyemi" />
<Avatar size="lg" name="Tobi Adeyemi" />
<Avatar size="xl" name="Tobi Adeyemi" />

Surfaces

Card

The site’s signature surface: a soft wash, an optional accent orb, and a hairline lit only at two corners.

From the siteCard

Protected by default

Biometric approval on every transfer above ₦50,000.

Swap in one tap

Move between USDC, BTC and naira at the rate you see.

Code
<Card>
  <div className="cx-icon-tile"><ShieldIcon /></div>
  <h3 className="cx-h4">Protected by default</h3>
  <p className="cx-small">Biometric approval on every transfer above ₦50,000.</p>
</Card>

<Card orb="top-right">…</Card>

Data

Stat

A labelled figure with an optional delta and sparkline, plus the allocation meter and the hero balance.

ExtensionStat, Sparkline
Portfolio value
$18,420.55
vs last week
Monthly spend
₦412,300
vs August
Cashback earned
₦6,840
Paid every Friday
Code
<Card>
  <Stat
    label="Portfolio value"
    value="$18,420.55"
    delta={3.62}
    foot="vs last week"
    sparkline={[12, 14, 13, 15, 17, 16, 19, 21]}
  />
</Card>

Table

A borderless data table for holdings and statements, with sortable headers and right-aligned figures.

Extension.cx- classes

Holdings

$18,420.55
AssetHolding
BitcoinBTC
$64,210.400.1825 BTC$11,718.40
EthereumETH
$3,120.851.42 ETH$4,431.61
SolanaSOL
$148.329.5 SOL$1,409.04
USD CoinUSDC
$1.00861.5 USDC$861.50
Code
<Card>
  <div className="cx-table-wrap">
    <table className="cx-table">
      <thead>
        <tr>
          <th scope="col">Asset</th>
          <th scope="col" className="r" aria-sort="none"><button type="button">Price</button></th>
          <th scope="col" className="r" aria-sort="none"><button type="button">24h</button></th>
          <th scope="col" className="r cx-hide-sm">Holding</th>
          <th scope="col" className="r" aria-sort="descending"><button type="button">Value</button></th>
        </tr>
      </thead>
      <tbody>
        <tr aria-selected="true">
          <td>
            <div className="cx-asset">
              <i aria-hidden="true">B</i>
              <div>Bitcoin<small>BTC</small></div>
            </div>
          </td>
          <td className="r">$64,210.40</td>
          <td className="r"><DeltaChip value={2.14} /></td>
          <td className="r cx-hide-sm">0.1825 BTC</td>
          <td className="r">$11,718.40</td>
        </tr>
      </tbody>
    </table>
  </div>
</Card>

List

Hairline-separated rows for transaction feeds and payee lists: leading token, main text, trailing figure.

Extension.cx- classes

Recent activity

See all
  • Tobi AdeyemiRent share · Today, 09:12
    +₦150,000.00Received
  • Shoprite LekkiVirtual card ···· 4821 · Yesterday
    −₦23,450.00Card
  • Ikeja Electric prepaidMeter 0451 2290 · Yesterday
    −₦15,000.00Pending
  • USDC depositFrom 0x7a3f…91c2 · 24 Sep
    +250.00 USDC≈ ₦387,125
Code
<ul className="cx-list">
  <li className="cx-list-row">
    <Avatar name="Tobi Adeyemi" />
    <div className="main">
      <strong>Tobi Adeyemi</strong>
      <small>Rent share · Today, 09:12</small>
    </div>
    <div className="end">
      <strong className="cx-gain">+₦150,000.00</strong>
      <small className="cx-muted">Received</small>
    </div>
  </li>
  <li className="cx-list-row">
    <div className="cx-icon-tile cx-icon-tile--sm"><BoltIcon /></div>
    <div className="main">
      <strong>Ikeja Electric prepaid</strong>
      <small>Meter 0451 2290 · Yesterday</small>
    </div>
    <div className="end">
      <strong>−₦15,000.00</strong>
      <Chip tone="warning" dot>Pending</Chip>
    </div>
  </li>
</ul>

Inputs

Text field

Labelled single-line, multi-line and select controls; Field wires the label, help and error text to the control for you.

ExtensionField, Input, Textarea
Must match the name on your bank account.
0 / 140
Code
<Field label="Account holder" help="Must match the name on your bank account.">
  <Input placeholder="Adaeze Okafor" autoComplete="name" />
</Field>

<Field label="Payout currency">
  <Select defaultValue="usd" options={[
    { value: 'usd', label: 'US Dollar (USD)' },
    { value: 'gbp', label: 'British Pound (GBP)', disabled: true },
  ]} />
</Field>

<Field label="Transfer note" optional>
  <Textarea value={note} maxLength={140} onChange={(e) => setNote(e.target.value)}
    aria-describedby="note-count" />
  <div className="cx-counter" id="note-count">{note.length} / 140</div>
</Field>

Amount input

A large, light-weight numeric field with a currency prefix for send, top-up and withdraw screens, paired with quick-amount chips.

ExtensionAmountInput, Field
Available: $1,840.50
Code
const [value, setValue] = useState('');
const over = Number(value || 0) > balance;

<Field label="Amount to send" help={`Available: $${fmt(balance)}`}
  error={over ? `That is more than your available $${fmt(balance)}.` : undefined}>
  <AmountInput value={value} onChange={(e) => setValue(clean(e.target.value))} placeholder="0.00" />
</Field>
<div className="cx-amount-chips" role="group" aria-label="Quick amounts">
  {[50, 100, 250, 500].map((q) => (
    <button key={q} type="button" onClick={() => setValue(String(q))}>${q}</button>
  ))}
  <button type="button" onClick={() => setValue(String(balance))}>Max</button>
</div>

OTP input

Segmented one-time-code and PIN entry that auto-advances, handles backspace and arrow keys, and accepts a pasted code.

ExtensionOtpInput, Button

We sent a 6-digit code to +234 ••• ••• 4471. (Demo code: 482913)

Code
const [code, setCode] = useState('');

<OtpInput
  value={code}
  onChange={setCode}
  onComplete={(v) => verify(v)}
  aria-label="Sign-in verification code"
/>
<p className="cx-small" role="status" aria-live="polite">{message}</p>

Copy field

Read-only value with a one-tap copy button for wallet addresses, account numbers and referral codes.

ExtensionCopyField, Field
TQ7mYx3hK9fRw2LpVb8NcE4sJd6uZa1Gt5
Send only USDT on the Tron network to this address.
Code
<Field label="Your USDT deposit address (TRC-20)"
  help="Send only USDT on the Tron network to this address.">
  <CopyField value="TQ7mYx3hK9fRw2LpVb8NcE4sJd6uZa1Gt5" aria-label="USDT deposit address" />
</Field>

Checkbox & radio

Native checkboxes and radios in an accent style, with a label that is part of the click target and an indeterminate state for “select all”.

ExtensionCheckbox, Radio
Code
<Checkbox label="Remember this device for 30 days" defaultChecked />
<Checkbox label="Email me a receipt for every transfer" />
<Checkbox label="Allow international card payments" disabled />
<Checkbox label="Two-factor sign-in (required)" checked disabled readOnly />

Switch

An on/off toggle for settings that take effect immediately, built on a native checkbox with role="switch".

ExtensionSwitch
  • Payment alertsPush notification for every debit and credit.
  • Round-up savingsMove spare change from card spend into your vault.
  • Hide balancesMask amounts on the home screen until you tap.
  • Crypto tradingAvailable once your ID check is approved.
Code
<ul className="cx-list">
  <li className="cx-list-row">
    <div className="main">
      <strong id="sw-push">Payment alerts</strong>
      <small id="sw-push-d">Push notification for every debit and credit.</small>
    </div>
    <Switch checked={on} onChange={(e) => setOn(e.target.checked)}
      aria-labelledby="sw-push" aria-describedby="sw-push-d" />
  </li>
</ul>

Slider

A native range input with an accent fill for picking a value within bounds, such as a savings percentage or slippage.

Extension.cx- classes
15%
On a $1,000 deposit, $150 goes to your savings vault.
Code
const [v, setV] = useState(15);

<div className="cx-field">
  <div className="cx-label">
    <label htmlFor="save">Auto-save from each deposit</label>
    <output htmlFor="save">{v}%</output>
  </div>
  <input id="save" className="cx-range" type="range" min={0} max={50} value={v}
    onChange={(e) => setV(Number(e.target.value))}
    aria-valuetext={`${v} percent`} aria-describedby="save-help"
    style={{ '--pct': `${(v / 50) * 100}%` } as React.CSSProperties} />
  <div className="cx-help" id="save-help">On a $1,000 deposit, ${v * 10} goes to savings.</div>
</div>

Choice card

Radio buttons presented as selectable cards, for choices that need a description or a price, like plans or networks.

Extension.cx- classes

Choose a plan

Code
<p id="plan-label">Choose a plan</p>
<div role="radiogroup" aria-labelledby="plan-label">
  <label className="cx-choice">
    <input type="radio" name="plan" value="plus" defaultChecked />
    <span className="cx-icon-tile cx-icon-tile--sm"><BoltIcon /></span>
    <span className="main">
      <strong>Plus</strong>
      <span className="cx-small">Multi-currency wallets and instant withdrawals.</span>
    </span>
    <span>$4/mo</span>
  </label>
  {/* …more cards */}
</div>

File upload

A dashed dropzone that accepts dragged or picked files, plus file rows with upload progress.

ExtensionProgress, Button
Code
<label className="cx-drop" htmlFor="docs" data-dragging={dragging || undefined}
  onDragOver={(e) => { e.preventDefault(); setDragging(true); }}
  onDragLeave={() => setDragging(false)}
  onDrop={(e) => { e.preventDefault(); setDragging(false); add(e.dataTransfer.files); }}>
  <UploadIcon />
  <strong>Drag your ID documents here</strong>
  <span className="cx-small">or click to browse. PDF, JPG or PNG up to 10 MB.</span>
  <input id="docs" type="file" multiple accept=".pdf,.jpg,.jpeg,.png"
    onChange={(e) => add(e.target.files)} />
</label>

Inline input

The site’s newsletter field: a pill with an input and a button inside, for one-field forms.

From the siteButton

One short FX and crypto brief a week. Unsubscribe any time.

Code
<form onSubmit={submit} noValidate>
  <label htmlFor="nl" className="cx-sr-only">Email address</label>
  <div className="cx-inline">
    <input id="nl" type="email" autoComplete="email" placeholder="Your email for rate alerts"
      aria-invalid={error || undefined} aria-describedby="nl-msg" />
    <Button type="submit" size="sm" arrow>Subscribe <ArrowIcon /></Button>
  </div>
  <p id="nl-msg" className="cx-small" role="status" aria-live="polite">{message}</p>
</form>

Date picker

A month calendar for choosing a single date or a range, with today marked, disabled dates and quick-pick presets.

ExtensionButton
 
MoTuWeThFrSaSu

No range picked yet.

Code
<div className="cx-cal" role="group" aria-label="Statement period">
  <div className="cx-cal-head">
    <Button variant="quiet" size="sm" icon aria-label="Previous month" onClick={prev}><ChevronLeft /></Button>
    <strong aria-live="polite">September 2026</strong>
    <Button variant="quiet" size="sm" icon aria-label="Next month" onClick={next}><ChevronRight /></Button>
  </div>
  <div className="cx-cal-grid">
    {['Mo','Tu','We','Th','Fr','Sa','Su'].map((d) => <span key={d} className="dow">{d}</span>)}
    {/* 42 days, Monday first: offset = (firstOfMonth.getDay() + 6) % 7 */}
    {cells.map((d) => (
      <button key={key(d)} type="button"
        className={cx(out(d) && 'out', isToday(d) && 'today', inRange(d) && 'in-range')}
        aria-pressed={isEnd(d) || undefined}
        aria-current={isToday(d) ? 'date' : undefined}
        aria-label={longLabel(d)}
        disabled={d > today}
        onClick={() => pick(d)}>{d.getDate()}</button>
    ))}
  </div>
  <div className="cx-cal-presets">
    <Button variant="ghost" size="sm" onClick={last7}>Last 7 days</Button>
  </div>
</div>

Feedback

Alert

Inline, persistent messages tied to a page or section, tinted by meaning. Banners span the full width for product-wide news.

ExtensionAlert
Scheduled maintenanceCard payments may be slow on Sunday between 02:00 and 03:00 UTC.
Transfer complete$1,250.00 reached Maya Okafor’s account.
Code
<Alert icon={<InfoIcon />} title="Scheduled maintenance">
  Card payments may be slow on Sunday between 02:00 and 03:00 UTC.
</Alert>
<Alert tone="gain" icon={<CheckIcon />} title="Transfer complete">…</Alert>
<Alert tone="warning" icon={<WarnIcon />} title="Verify your identity">…</Alert>
<Alert tone="loss" icon={<CrossIcon />} title="Payment declined">…</Alert>

Toast

Brief, self-dismissing confirmations of something the user just did, stacked bottom-right (full width on mobile).

Extensiontoast, Toast, ToastViewport
Code
import { toast } from '@/components/ui';

toast({ tone: 'gain', message: '$240.00 sent to Daniel Reyes' });
toast({ tone: 'loss', message: 'Card ••4821 was declined' });
toast({ tone: 'warning', message: 'Rate locked for 30 seconds', duration: 8000 });

// once, in app/layout.tsx
<ToastViewport />

Progress

Show how far along a task or goal is: a linear bar, a ring for compact goals, a spinner for short waits.

ExtensionProgress
Uploading statement64%
Holiday fund$2,160 of $3,000
Checking with your bank…
Code
<Progress value={64} aria-labelledby="upload-label" />
<Progress value={72} tone="gain" aria-labelledby="goal-label" />
<Progress indeterminate label="Connecting bank account" />

Steps

A numbered progress track for multi-step flows like sending money or onboarding.

ExtensionSteps
  1. Recipient
  2. Amount
  3. Review
  4. Done
Code
<Steps aria-label="Transfer progress" steps={['Recipient', 'Amount', 'Review', 'Done']} current={2} />

Skeleton

Shimmering placeholders shaped like the content that is loading, so layout does not jump.

ExtensionSkeleton
Code
<Card role="status" aria-busy="true" aria-label="Loading balance">
  <Skeleton width={96} height={12} />
  <Skeleton width="70%" height={40} radius={10} />
  <Skeleton width={120} height={20} radius={999} />
</Card>

Empty state

What a list or panel shows when there is nothing yet, or when loading failed, with one way forward.

ExtensionEmptyState

No transactions yet

Add money or receive a payment and it will show up here.

Code
<EmptyState
  icon={<ReceiptIcon />}
  title="No transactions yet"
  description="Add money or receive a payment and it will show up here."
  action={<Button size="sm">Add money</Button>}
/>

Overlays

Drawer

A side panel on desktop and a bottom sheet on mobile, for tasks with a few fields like sending money.

ExtensionDrawer

Send money

Available: $3,214.80
Code
<Drawer
  open={open}
  onClose={() => setOpen(false)}
  title="Send money"
  footer={<>
    <Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
    <Button type="submit" form="send-form">Send</Button>
  </>}
>
  <form id="send-form" onSubmit={submit}>
    <Field label="To"><Select options={contacts} /></Field>
    <Field label="Amount" error={error}><AmountInput value={amount} onChange={…} /></Field>
    <Field label="Note" optional><Textarea rows={3} /></Field>
  </form>
</Drawer>

Popover

A small non-modal panel that explains something in place, like how an exchange rate is priced.

Extension.cx- classes
How we price exchange

You get the mid-market rate with a flat 0.4% fee shown before you confirm. The rate is held for 30 seconds once you review.

Fee breakdown
Code
<div class="cx-popover">
  <b>How we price exchange</b>
  <p class="cx-small">You get the mid-market rate with a flat 0.4% fee…</p>
  <a class="cx-link cx-small" href="/fees">Fee breakdown</a>
</div>

Tooltip

A CSS-only label that appears above icon buttons on hover and keyboard focus.

Extension.cx- classes
Code
<span class="cx-tip" data-tip="Copy account number">
  <button class="cx-btn cx-btn--ghost cx-btn--icon" aria-label="Copy account number"><svg>…</svg></button>
</span>

Content

Accordion

The site’s numbered FAQ: native <details> rows with a rotating plus, optionally one open at a time.

From the siteAccordion
01How long does a transfer take?
Transfers between wallets arrive in seconds. Bank payouts usually land the same business day, and never take more than two.
02Are there fees for holding multiple currencies?
No. Holding balances is free. You only pay a flat, upfront fee when you convert between currencies.
03What happens if I lose my card?
Freeze it from the app instantly, then order a replacement. Your virtual card keeps working while the new one ships.
04Is my money protected?
Customer balances are held in segregated accounts at partner banks, separate from company funds.
Code
<Accordion
  numbered
  exclusive
  items={[
    { title: 'How long does a transfer take?', content: 'Transfers between wallets arrive in seconds…', defaultOpen: true },
    { title: 'Are there fees for holding multiple currencies?', content: 'No. Holding balances is free…' },
    { title: 'What happens if I lose my card?', content: 'Freeze it from the app instantly…' },
  ]}
/>

Marquee

An endlessly scrolling logo strip with faded edges, for social proof on marketing pages.

From the site.cx- classes
NorthwindApertureHalcyonMeridianLumenBrightfold
Code
<div class="cx-marquee" role="region" aria-label="Partners">
  <div>
    <span class="logo">…Northwind</span>
    <span class="logo">…Aperture</span>
    <!-- …rest of the set… -->
    <!-- duplicate set for the loop -->
    <span class="logo" aria-hidden="true">…Northwind</span>
    <span class="logo" aria-hidden="true">…Aperture</span>
  </div>
</div>