skynet

Components

24 primitives that carry their own accessibility

Each is styled only through tokens and a type role, and each brings its labels, roles, focus treatment and target sizes with it. The demos below are the real components. Hold Alt on this page: everything that abbreviates reveals its full value.

text

Eyebrow

The mono line above a section title.

How scoring works
Use
  • To label a section before its heading.
Avoid
  • As a heading on its own. It labels; the Heading speaks.
Accessibility, guaranteed
  • Decorative rule is CSS, not content.
Props of Eyebrow
PropTypeDefaultDescription
children*ReactNodeTwo to four words.
tokens: type.eyebrow, color.text.muted, color.text.accent
<Eyebrow>How scoring works</Eyebrow>

Heading

A heading at any level with the display type roles. An <em> inside is the accent word.

Ranked on edge, not hit rate

Use
  • Page and section titles.
Avoid
  • Choosing the level for its size. Level is structure; size is a prop.
Accessibility, guaranteed
  • Level and size are separate, so the outline stays correct whatever the visual weight.
Props of Heading
PropTypeDefaultDescription
level1 | 2 | 3 | 42Document outline level.
sizedisplay-xl | display-lg | display-md | display-sm | headingdisplay-mdVisual size, independent of level.
tokens: type.display-*, type.heading, color.text.primary, color.text.accent
<Heading level={1} size="display-lg">Ranked on edge, <em>not hit rate.</em></Heading>

Hint

A small ? beside a term that explains it on hover, focus or tap.

Edge Price move in the called direction, in units of the asset’s volatility.
Use
  • A column header whose name is a term of art, like Edge.
  • A label that needs one short sentence of context.
Avoid
  • More than one sentence: it is a definition, not an explanation.
  • Anything the reader must read to act: put it in a Callout or in the copy.
  • Interactive content inside the bubble; it closes on blur.
Accessibility, guaranteed
  • The trigger is a button described by the tooltip text (aria-describedby), so the explanation is read without hovering.
  • Opens on focus, closes on Escape and blur.
  • 24px target.
Props of Hint
PropTypeDefaultDescription
label*stringAccessible name of the trigger, e.g. "What is edge?"
tokens: size.control-md, panel.*, shadow.raised, motion.reveal, motion.exit, type.body-sm
Edge <Hint label="What is edge?">Price move in the called direction, in units of the asset's volatility.</Hint>

Note

Supporting text under a control or section, in caption type and the muted colour.

devnet · one signature · network fee in SOL

Use
  • The line under a submit button that says what will happen.
  • A one-line aside.
Avoid
  • Anything the reader must not miss: use Callout.
Accessibility, guaranteed
  • Contrast of color.text.muted is body-text grade, so a note is readable, not decorative.
tokens: type.caption, color.text.muted
<Note>devnet · one signature · network fee in SOL</Note>

Code

Inline or block code, addresses, commands.

skynet verify --provider <wallet> --api-url https://<host>
Use
  • Commands the reader will paste.
Avoid
  • Emphasis. Code style means literal.
Accessibility, guaranteed
  • Blocks scroll horizontally rather than wrapping a command.
Props of Code
PropTypeDefaultDescription
blockboolean
tokens: type.code, color.surface.sunken
<Code block>skynet verify --provider &lt;wallet&gt;</Code>
layout

RevealDetailsProvider

Hold Alt anywhere to see underlying detail: full addresses, four-decimal edges.

Wraps the app. Hold Alt anywhere on this page to see addresses and edges expand.

Use
  • Once, around the app.
Avoid
  • Hiding anything essential behind it. It reveals precision, not information.
Accessibility, guaranteed
  • Everything revealed is also available without the modifier: as a title, or visually hidden text.
tokens: none
<RevealDetailsProvider>{children}</RevealDetailsProvider>
input

Button

The one way to trigger an action.

The first one goes busy for a moment when pressed. The disabled one stays focusable and explains itself on hover and to assistive tech.

Use
  • Submitting, committing, signing.
  • primary for the one main action on a view, secondary for the rest, ghost inline.
Avoid
  • Navigation. Use a link.
  • More than one primary per view.
Accessibility, guaranteed
  • Uses aria-disabled, not disabled, so keyboard users can reach it and hear why.
  • Minimum target 24px (WCAG 2.5.8); default 40px.
  • Visible focus ring on keyboard focus only.
  • Label swaps while busy, and aria-busy is set.
Props of Button
PropTypeDefaultDescription
variantprimary | secondary | ghostprimary
sizemd | smmd
disabledbooleanUnavailable but still focusable; pair with disabledReason.
disabledReasonstringWhy it is unavailable. Shown as the title.
busybooleanIn flight: swaps the label and ignores clicks.
busyLabelstringWorking
blockbooleanFull width.
staticbooleanNo press scale, for an action that deserves stillness.
tokens: button.*, color.accent.fill, color.text.on-accent, type.button, motion.hover
<Button busy={sending} busyLabel="Waiting for signature">Commit signal</Button>

Field

Label, hint and error wired to one control.

A Coinbase pair. Click the label: the field takes focus.

Use
  • Around every Input, Select or Textarea.
Avoid
  • A control without a Field. The control will throw.
Accessibility, guaranteed
  • Clicking the label focuses the control.
  • Hint and error are in aria-describedby.
  • Error sets aria-invalid and is announced.
Props of Field
PropTypeDefaultDescription
label*ReactNode
hintReactNodeRead with the control by assistive tech.
errorReactNodeAnnounced (role=alert); marks the control invalid.
tokens: field.*, type.label, type.caption, color.status.loss
<Field label="Instrument" hint="A Coinbase pair"><Input value={pair} onChange={...} /></Field>

Input / Select / Textarea

Styled native controls. They read their wiring from the surrounding Field.

3 to 24 characters

Use
  • Inside a Field.
Avoid
  • Outside a Field.
Accessibility, guaranteed
  • 16px text so iOS does not zoom on focus.
  • Border meets 3:1 against the page (color.border.control).
  • Placeholder is never the label.
Props of Input / Select / Textarea
PropTypeDefaultDescription
...nativeInputHTMLAttributesEverything the native element takes.
tokens: field.*, type.control, color.border.control
<Field label="Thesis"><Textarea rows={3} /></Field>

Combobox

A text input with a listbox of suggestions. The parent searches; this handles keys and ARIA.

Type a letter, then use the arrow keys.

Use
  • Picking an instrument from thousands.
  • Any field where the user types to narrow a list.
Avoid
  • Under ten fixed choices: use Select.
  • Free text with no suggestions: use Input.
Accessibility, guaranteed
  • role=combobox with aria-expanded, aria-controls and aria-activedescendant; options are never focused, so focus stays in the field.
  • ArrowDown/Up move, Enter picks, Escape closes, Tab leaves.
  • Must sit inside a Field for its label and description.
Props of Combobox
PropTypeDefaultDescription
value*stringTyped text, owned by the parent.
onChange*(text) => void
options*ComboboxOption[]id, label, description?, badge?
onSelect*(option) => void
loadingbooleanShows Searching in the list.
emptyTextReactNodeShown when nothing matches.
tokens: field.*, shadow.raised, motion.reveal
<Field label="Instrument"><Combobox value={q} onChange={setQ} options={hits} onSelect={pick} loading={busy} emptyText="No match" /></Field>

SegmentedControl

One choice among two to five options.

Arrow keys move the selection. Tab leaves the group.
Use
  • Direction (long / short).
  • Conviction (1 / 2 / 3).
  • Timeframe.
Avoid
  • More than five options. Use a Select.
  • Multiple selection.
Accessibility, guaranteed
  • A radiogroup: arrow keys move, Tab leaves.
  • Selection is aria-checked plus a check glyph, never colour alone.
Props of SegmentedControl
PropTypeDefaultDescription
label*stringNames the group for assistive tech.
showLabelbooleanAlso show the label above the group, like a Field.
options*{ value, label, description? }[]
value*T
onChange*(value: T) => void
tokens: field.height, color.accent.fill, color.border.control
<SegmentedControl label="Direction" options={[{ value: "long", label: "Long" }, { value: "short", label: "Short" }]} value={dir} onChange={setDir} />
data

Badge

A short status word: house, provisional, established.

houseprovisionalestablishedmatches on-chain hashdoes not matchpending
Use
  • One word beside the thing it qualifies.
Avoid
  • Sentences.
  • Colour as the only meaning: the word is the meaning.
Accessibility, guaranteed
  • Always text.
Props of Badge
PropTypeDefaultDescription
toneneutral | accent | win | loss | pendingneutral
tokens: badge.*, type.label, color.status.*
<Badge tone="neutral">house</Badge>

Outcome

Win, loss, void or pending, as glyph plus word.

WinLossVoidPending
Use
  • Wherever an outcome is shown.
Avoid
  • A coloured dot with no text.
Accessibility, guaranteed
  • Glyph is aria-hidden; the word carries the meaning.
  • Win and loss colours are reserved for outcomes and never decorate anything else.
Props of Outcome
PropTypeDefaultDescription
outcome*win | loss | void | pending
tokens: color.status.*, type.label
<Outcome outcome="win" />

EdgeValue

A signed risk-adjusted edge with its uncertainty and sample size beside it.

+0.41±0.08n=127positive edge+0.10±0.09n=173edge not yet distinguishable from zero−0.24n=12provisionalnegative edge0.00n=200zero edge—Hold Alt for four decimals.
Use
  • The board, provider pages, signal pages.
Avoid
  • Showing an edge without n. The sample size is part of the number.
  • Colouring an edge that is not significant. Pass `significant` wherever a standard error exists; grey is the honest colour for a record that is still noise.
Accessibility, guaranteed
  • Sign is a glyph plus colour plus visually hidden text; an unclear record says so in the hidden text and the title.
  • Tabular figures so columns align.
  • Hold Alt to see four decimals; the exact value is also the title.
Props of EdgeValue
PropTypeDefaultDescription
value*number | nullnull renders a dash.
nnumberScored bets.
senumber | nullStandard error of the mean bet edge, shown as ±.
significantbooleanFalse withholds the colour: the record is under two standard errors from zero.
provisionalbooleanFewer than k scored bets.
sizemd | lgmdlg for a stat tile.
tokens: type.data, color.status.win, color.status.loss, color.text.secondary
<EdgeValue value={0.1} se={0.09} significant={false} n={173} />

Address

A wallet address, shortened in the middle.

299M…rpDh299MJfarv7tkAqZmf3VqUyuCcCnm3y3bXyV5AxMerpDhHold Alt to see the whole address.
Use
  • Anywhere an address appears.
Avoid
  • Truncating at the end: the end is what distinguishes addresses.
Accessibility, guaranteed
  • The full address is available to assistive tech and as the title.
  • Hold Alt to reveal it inline.
  • A full address breaks anywhere rather than overflowing.
Props of Address
PropTypeDefaultDescription
value*string
fullbooleanNever shorten.
tokens: type.code, color.text.secondary
<Address value={wallet} />

ProviderLabel

How a provider is named: display name if any, address always, house badge if ours.

Test Desk299M…rpDh299MJfarv7tkAqZmf3VqUyuCcCnm3y3bXyV5AxMerpDhvol regime2Qzw…84y32Qzw5UftWbNtoVvExNE15SfNpvndJ53icMffdpnN84y3house2FJW…NVnW2FJWJPJzzuDbydk4PFocmnDeMwQK1PNSY4qpE15SNVnW
Use
  • Every list of providers or signals.
Avoid
  • Showing a name without the address. A name is a label the wallet chose.
Accessibility, guaranteed
  • Self-declared names carry an explanatory title.
Props of ProviderLabel
PropTypeDefaultDescription
address*string
namestring | nullHouse label or self-declared name.
houseboolean
tokens: type.body-sm, type.code, badge.*
<ProviderLabel address={p.address} name={p.name} house={p.house} />

DataTable

Tabular data as a real table.

Leaderboard, excerpt
ProviderEdgeHit rate
vol regime2Qzw…84y32Qzw5UftWbNtoVvExNE15SfNpvndJ53icMffdpnN84y3house+0.41n=26positive edge73.1%
Test Desk299M…rpDh299MJfarv7tkAqZmf3VqUyuCcCnm3y3bXyV5AxMerpDh−0.05n=9provisionalnegative edge33.3%
coin flip2FJW…NVnW2FJWJPJzzuDbydk4PFocmnDeMwQK1PNSY4qpE15SNVnWhouse+0.08n=69positive edge56.5%
Use
  • The board, signal lists.
Avoid
  • Layout.
  • Cards pretending to be rows.
Accessibility, guaranteed
  • caption, th scope=col.
  • Numeric columns end-aligned in tabular figures.
  • The scroll region is focusable, so keyboard users can scroll a wide table.
Props of DataTable
PropTypeDefaultDescription
caption*stringNames the table; can be visually hidden.
columns*Column<T>[]header, cell renderer, align.
rows*T[]
rowKey*(row) => string
rowHref(row) => stringWhole row is a pointer target for the row's destination; the row must still hold a real link to it.
navigate(href) => voidClient-side navigation, e.g. router.push.
tokens: table.*, type.label, type.body-sm
<DataTable caption="Leaderboard" columns={cols} rows={providers} rowKey={(p) => p.address} />

Stat

One number with its label. StatGroup lays several out in a responsive grid.

502
Signals committed
393
Marked at close
5
Established records
+0.41n=127positive edge
Edge
60
Wins
49
Losses
30
Pending
Use
  • Counts on a landing page.
  • A provider's totals. Put an EdgeValue with size="lg" in the value slot for the edge.
Avoid
  • Colouring a count that is not an outcome. `tone` is for win, loss and pending counts.
Accessibility, guaranteed
  • Tabular figures.
  • Tone is never the only cue: the label says what the number is.
Props of Stat
PropTypeDefaultDescription
value*ReactNode
label*ReactNode
tonewin | loss | pendingOutcome counts only.
sizesm | md | lgmdlg is the landing-page headline number; sm for a value that is words or a date, not a number.
tokens: type.display-sm, type.label, color.status.*
<StatGroup><Stat value={324} label="Signals committed" /><Stat value={<EdgeValue value={0.41} n={127} size="lg" />} label="Edge" /></StatGroup>

Checklist

Facts the page has checked, each with a verdict mark and the evidence in one line.

  • Passed: Timestamped on Solana
    2026-09-28 19:10:10 UTC · slot 412 038 117
  • Passed: Signed by the SSP-1 program
    The memo carries the provider’s signature and the program’s.
  • Failed: Memo matches this page
    Timeframe differs: chain says 4H, page says 1H.
  • Pending: Marked from public candles
    Closes 2026-09-28 21:12 UTC.
Use
  • On-chain proof of a signal: timestamp, program signature, memo match, marks.
  • Any place the product claims something is verified: show the check, not the instructions.
Avoid
  • To-do lists or forms.
  • Claims the page did not actually check.
Accessibility, guaranteed
  • The state is read out in words before the label; the coloured mark is never the only cue.
Props of Checklist
PropTypeDefaultDescription
label*stringAccessible name of the list.
items*{ label, detail?, state: pass | fail | pending | unknown }[]
tokens: color.status.*, size.target-min, type.body-sm, type.caption
<Checklist label="On-chain proof" items={[{ state: "pass", label: "Timestamped on Solana", detail: "2026-09-28 19:10:10 UTC · slot 412…" }]} />
container

Dialog

A modal on the native <dialog> element: focus trap, Escape, inert page and focus return come from the browser.

Commit LONG BTC?

This goes on-chain with your signature. It cannot be edited or deleted.

Timeframe
1H
Close
in 2 hours
Conviction
2 of 3
Use
  • Confirming an action that cannot be undone (role=alertdialog).
  • A short form that must finish before anything else.
Avoid
  • Information that could sit on the page.
  • Anything with its own navigation.
Accessibility, guaranteed
  • showModal(): focus moves in, Tab cannot leave, the page behind is inert, Escape closes, focus returns to the opener.
  • Labelled by its title and described by its description.
  • Centre transform origin: modals have no trigger to grow from.
Props of Dialog
PropTypeDefaultDescription
open*boolean
onClose*() => voidAny close: Escape, backdrop, Cancel.
title*ReactNode
descriptionReactNode
actionsReactNodeButtons; primary last.
roledialog | alertdialogdialogalertdialog ignores backdrop clicks.
tokens: color.surface.scrim, shadow.overlay, radius.lg, motion.reveal, motion.exit
<Dialog open={confirming} onClose={() => setConfirming(false)} role="alertdialog" title="Commit LONG BTC?" description="One signature. It cannot be edited or deleted." actions={<><Button variant="secondary" onClick={cancel}>Back</Button><Button onClick={commit}>Sign and commit</Button></>} />

Panel

A raised surface with a hairline border.

Connect to commit

Sign in with email or a Solana wallet.

Use
  • Forms, record cards.
Avoid
  • Nesting panels.
  • Panels as page structure; sections are divided by rules.
Accessibility, guaranteed
  • Presentational.
Props of Panel
PropTypeDefaultDescription
asdiv | section | formdiv
tokens: panel.*
<Panel as="form" onSubmit={...}>…</Panel>

Callout

A short message that needs attention: a note, a warning, an error.

Marked once the candle containing the close is final.
Signal committed. It appears on the board once indexed.
The devnet faucet is rate limited right now.
Use
  • Draft notices, failures, empty states with a reason.
Avoid
  • Marketing copy.
Accessibility, guaranteed
  • Tone is a glyph plus a coloured rule, with a live-region role that matches its urgency.
Props of Callout
PropTypeDefaultDescription
toneneutral | accent | loss | pendingneutral
rolestatus | alert | noteDefaults to alert for loss, note otherwise.
tokens: color.border.*, color.status.*
<Callout tone="pending">Marked once the close candle is final.</Callout>