Quickstart
From zero to a rendered widget in six steps. Prerequisite: access - membership of the widget-consumers team and a scoped GitHub token.
1. Install the CLI
The signalos CLI ships privately from GitHub Packages as @signallabsai/cli. Once per machine, store your CLI token (classic PAT, read:packages scope - creation walkthrough: Get access):
npm config set //npm.pkg.github.com/:_authToken <your token>
Then, in your app repo, route the scope and install:
echo "@signallabsai:registry=https://npm.pkg.github.com" >> .npmrc pnpm add -D @signallabsai/cli
Commit the repo's .npmrc (it holds no secrets - the token lives in your user-level npm config). Every npx signalos … command below now resolves to the installed CLI.
2. Set the registry token
Use your registry token (fine-grained PAT, repo: signal-widgets only, permission: Contents read-only - creation walkthrough: Get access) and export it permanently:
npx signalos token set github_pat_XXXX
One command, cross-platform: it persists the token for your shell (setx on Windows, .zshrc/.bashrc/fish on macOS & Linux), writes it to this project's .env.local (and gitignores it), then verifies it against the registry. Prefer doing it by hand? Any mechanism that sets the SIGNALOS_REGISTRY_TOKEN environment variable works.
3. Configure your app (once)
In a Next.js/React app that already uses Tailwind v4 and has run shadcn init:
npx signalos init
This adds the @signalos registry to components.json, verifies your token against the registry, and creates signalos.lock.json. No CLI? Add the block by hand:
{
"registries": {
"@signalos": {
"url": "https://raw.githubusercontent.com/SignalLabsAI/signal-widgets/main/r/{name}.json",
"headers": { "Authorization": "Bearer ${SIGNALOS_REGISTRY_TOKEN}" }
}
}
}4. Pull your first widget
npx signalos add data-table
One command pulls the widget and everything it needs: the design tokens (src/styles/signalos.css), the cn() util, and the primitives it builds on - then installs npm dependencies and records versions in the lockfile.
5. Wire the tokens (once)
In your globals.css, after @import "tailwindcss";:
@import "tailwindcss"; @import "../styles/signalos.css"; @custom-variant dark (&:is(.dark *));
6. Render it
import { DataTable } from "@/components/widgets/data-table/DataTable"
export default function SignalsPage() {
return (
<DataTable
columns={columns} // ColumnDef<YourRow>[]
data={rows} // your domain data, mapped in the screen
emptyMessage="No signals yet."
/>
)
}The screen maps your domain types into widget props - the widget never imports them. That seam is what keeps every pulled widget portable.
Pulling a full page
Pages work the same way - pull a variant, mount it in a route:
npx signalos add login-01
# src/app/login/page.tsx
import { Login01 } from "@/components/blocks/login-01/Login01"
export default function LoginPage() {
return <Login01 onSubmit={({ email, password }) => signIn(email, password)} />
}