Skip to Content
Card SDKUI kit & styling

UI kit & styling

Inside its box, a card is an ordinary web page: any framework, any CSS, canvas, WebGL, WebAssembly. Two ways to style it:

  • Native look — the optional kit ui.css: a small dark set of buttons, fields, lists and badges on the app’s live theme. Cards built with it look like part of NeuroSquad and follow its theme.
  • Your own design — ignore the kit. The theme variables are still there if you want to borrow a colour.

The UI kit

<link rel="stylesheet" href="vendor/ui.css" /> <!-- plain template -->
import '@neurosquad/card-sdk/ui.css' // with a bundler (React template)

Put class="ns-kit" on <body> for the base typography, background and scrollbars, then use the classes:

<body class="ns-kit"> <header class="ns-spread"> <h1 class="ns-title ns-truncate">Test radar</h1> <span class="ns-badge ns-badge--danger">3 failed</span> </header> <ul class="ns-list ns-scroll"> <li class="ns-list-item"><span class="ns-dot ns-dot--danger"></span> checkout › pays</li> <li class="ns-list-item"><span class="ns-dot ns-dot--success"></span> auth › logs in</li> </ul> <label class="ns-field"> <span class="ns-label">Filter</span> <input class="ns-input" placeholder="checkout" /> </label> <div class="ns-row"> <button class="ns-btn ns-btn--primary">Run</button> <button class="ns-btn ns-btn--ghost ns-btn--sm">Clear</button> </div> </body>
GroupClasses
Layoutns-kit, ns-stack (vertical), ns-row (horizontal), ns-spread (space-between), ns-grow, ns-scroll, ns-pad, ns-divider
Surfacesns-surface, ns-surface--inset, ns-callout, ns-callout--success, ns-callout--danger, ns-empty
Textns-title, ns-subtitle, ns-muted, ns-small, ns-mono, ns-truncate, ns-kbd, ns-help, ns-error
Buttonsns-btn + --primary, --secondary, --outline, --ghost, --danger, --sm, --lg, --icon
Fieldsns-field, ns-label, ns-input, ns-textarea, ns-select, ns-switch
Listsns-list, ns-list-item
Statusns-badge + --accent, --success, --warning, --danger; ns-dot + the same; ns-spinner, ns-progress

Outside the app (a browser preview), the kit falls back to the app’s dark theme, so the preview looks right too.

Theme variables

connect() puts the app’s live theme on <html> as --ns-<token> variables — --ns-background, --ns-surface, --ns-foreground, --ns-muted, --ns-accent, --ns-success, --ns-warning, --ns-danger, their -foreground and -soft variants, --ns-border, --ns-focus, --ns-field-*, --ns-radius, --ns-font-sans, --ns-font-mono and more (the full list is on Theme). Use them in your own CSS and your card follows the app:

:root { color-scheme: dark; } body { margin: 0; background: var(--ns-background, #0b0b0f); color: var(--ns-foreground, #fafafa); font: 13px/1.45 var(--ns-font-sans, system-ui, sans-serif); } .chip { border-radius: calc(var(--ns-radius, 0.5rem) * 2); background: var(--ns-accent-soft); color: var(--ns-accent-soft-foreground); }

Give the variables fallbacks (as above) if your page must also render outside the app. To keep your own colours untouched, connect with connect({ theme: false }).

Sandbox rules for pages

Your page is served from its own package with a strict content security policy. In practice:

  • No inline scripts or onclick="…" attributes — put code in .js files. eval and new Function are blocked too (WebAssembly is allowed).
  • No external files. Scripts, styles, fonts and images must be inside the package; a <link href="https://fonts…"> or <img src="https://…"> is blocked. data: and blob: images work. For remote data, use card.net.fetch and turn images into blob: URLs.
  • Inline styles are fine (style="…" and <style>).
  • Relative URLs. The page is served from nscard://<id>/<path>; build with relative asset paths (Vite: base: './', as in the React template).
  • No localStorage, cookies, alert, pop-ups, downloads, fullscreen, camera or microphone. Use card.storage, card.ui.confirm, card.openLink.
  • No writing to the clipboard from the page. copy/cut handlers and document.execCommand('copy') do nothing (otherwise any card could replace the clipboard on a click). Use card.copyText; the user’s own copy of selected text still works.
  • No nested frames. iframe, frame, object and embed elements are removed.
  • Only blob workers. new Worker('worker.js') fails in the card’s origin; fetch the script, wrap it in a Blob and start new Worker(URL.createObjectURL(blob)).
  • Never trust message events. Other cards can postMessage your frame.

npx @neurosquad/card-sdk validate warns about inline scripts, inline handlers and external files in your entry page.

Forms never navigate. A <form>’s submit event fires and your handler runs, but the app cancels the submission itself and form.submit() does nothing — a card page cannot post anywhere or reload. Call event.preventDefault() in your handler anyway and do the work in JavaScript.

Designing for the canvas

  • Small and large. A card is often 400×300 and sometimes expanded to fill the screen. Make both look finished: use card.lifecycle.onExpanded or useExpanded() to switch layouts.
  • Empty, loading, error. Show something sensible before data arrives, when there is none, and when a call fails.
  • The overview tile is your card zoomed out. Keep setOverview current with the one fact that matters.
  • Quiet when unseen. Stop animations when usePaused() is true — a canvas can hold fifty cards.
  • The header is the app’s. Do not repeat the card’s name or a close button inside the body; the header already has them.

Anything inside the card body is the card’s own, and users are told so. Do not imitate NeuroSquad’s dialogs or ask for passwords and keys inside your card — use secret settings, which the app asks for in its own form.