Set up Squad SDK in a Next.js or Vite project (both Tailwind v4) and install components from the registry.
Set up your project
Pick your framework. Every registry item is a framework-agnostic React component, so only the scaffold, the stylesheet location, and how you wire fonts + the
squad-ui body class differ.1. Scaffold + init. Create the app, generate the npm lockfile, then run shadcn init to write components.json.
$Terminal
# 1) Next.js (15+ recommended; the docs site runs on 16)
npx create-next-app@latest my-app
cd my-app
# 2) Generate the npm lockfile so shadcn's package-manager detector picks
# npm (otherwise a stray ~/yarn.lock or ~/pnpm-lock.yaml further up the
# tree can make it fail with "spawn yarn ENOENT" on the first add).
npm install
# 3) shadcn CLI — `init` writes `components.json` (path aliases for the
# CLI). `add theme.json` will otherwise prompt for it on the first run
# and the `--yes` flag does NOT auto-confirm that specific prompt.
npx shadcn@latest init --yes -d2. Install the theme (tokens + cn helper + npm deps), then @import it from globals.css.
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/theme.json --yes
# OPTIONAL — branded type (Albra headings + Inter body). Ships Albra as
# base64-embedded @font-face rules so there's nothing to download manually.
npx shadcn@latest add https://sdk-components.thesqd.com/r/typography.json --yesCSSsrc/app/globals.css
/* src/app/globals.css */
@import "tailwindcss"; /* Tailwind v4 base */
@import "tw-animate-css"; /* enter/exit animations used by modals, popovers, etc. */
@import "shadcn/tailwind.css"; /* shadcn base utilities (cn, etc.) */
@plugin "@tailwindcss/typography"; /* prose styles required by RichTextEditor */
@import "../styles/squad-ui.css"; /* Squad tokens — dropped in by the theme install */
/* The squad-ui theme references --font-sans (body) and --font-heading.
Wire them up in layout.tsx (next/font), or set fallbacks here if you
want to ship without web fonts:
.squad-ui {
--font-sans: system-ui, -apple-system, "Segoe UI", sans-serif;
--font-heading: var(--font-sans);
}
Squad components self-apply `squad-ui` to their roots — wrapping <body>
in the class makes the tokens available to the rest of your app too. */
3. Wire fonts + the body class. Squad references --font-sans / --font-heading; wire them with next/font and put squad-ui on <body>.
TSsrc/app/layout.tsx
// src/app/layout.tsx
import { Inter } from "next/font/google";
// Squad UI components reference --font-sans (body) and --font-heading.
// Wire Inter to both so headings and body type render correctly out of
// the box. Override --font-heading in your overrides block if you want
// a different display font (e.g. Albra in the docs site).
const inter = Inter({ variable: "--font-sans", subsets: ["latin"] });
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={inter.variable}>
<body className="squad-ui">{children}</body>
</html>
);
}2. Install components and blocks
Each registry item ships as one self-contained file. The CLI walks `registryDependencies` to install primitives a block depends on, and runs `npm install` for any npm packages declared in `dependencies`.
$Terminal
# Single component — primitives + npm deps auto-resolve
npx shadcn@latest add https://sdk-components.thesqd.com/r/badge.json
# Multiple at once
npx shadcn@latest add \
https://sdk-components.thesqd.com/r/dialog.json \
https://sdk-components.thesqd.com/r/alert-dialog.json
# A block — pulls every `registryDependency` (button, badge, tooltip, …)
# and runs `npm install` for declared `dependencies` (@dnd-kit/*, lucide-react, …)
npx shadcn@latest add https://sdk-components.thesqd.com/r/kanban-board-showcase.jsonTSExample usage
import { Badge } from "@/components/ui/badge";
import { KanbanBoardShowcase } from "@/components/blocks/kanban-board-showcase";
export function Page() {
return (
<div>
<Badge>Shipped</Badge>
<KanbanBoardShowcase />
</div>
);
}3. Override tokens (optional)
Squad ships sensible defaults for every token. To brand the components for your project, set your own values on `.squad-ui` after the import — source-order wins.
CSSsrc/app/globals.css
/* src/app/globals.css — add AFTER the squad-ui.css import.
Source-order wins, so anything you set here replaces Squad defaults
on every component, light + dark. */
.squad-ui {
--primary: oklch(0.62 0.22 18);
--radius: 0.5rem;
}
/* Per-mode overrides scope under the same dark selector Squad uses. */
.dark .squad-ui,
.squad-ui.dark {
--primary: oklch(0.78 0.16 18);
}Common pitfalls
Hands-on snags that bite when setting Squad UI up in a fresh project. Skim this before opening a bug report.
TXTShared (Next.js + Vite)
Most of these used to be manual setup steps — `theme.json` and individual
component manifests now resolve them automatically. Listed here so you know
what each install IS doing under the hood, and what's still on you.
✓ Auto-resolved by `shadcn add theme.json`:
- `src/lib/utils.ts` (the `cn` helper every component imports as `@/lib/utils`)
- npm: `clsx`, `tailwind-merge`, `tw-animate-css`, `@tailwindcss/typography`, `shadcn`
- `src/styles/squad-ui.css` (the tokens scoped under `.squad-ui`)
- `globals.css` patches: `@import` lines for `tw-animate-css`, `squad-ui.css`,
the `@tailwindcss/typography` plugin, and a *conditional* system-font
fallback under `.squad-ui` (self-referencing var fallback — only kicks
in when `--font-sans` / `--font-heading` aren't set higher in the
cascade, so it never fights `next/font`).
✓ Auto-resolved by individual component installs:
- All `registryDependencies` (e.g. installing `alert-dialog` pulls `alert`,
`button`, `dialog`, etc. recursively).
- All npm `dependencies` declared on the manifest (e.g. `@dnd-kit/*` for
the kanban block, `react-filepond` for file-upload-beta).
- Per-block extras: e.g. `file-upload-beta` ships `src/app/filepond.css`,
`src/app/api/s3-presign/route.ts`, a `.env.example` template, AND an
`@import "./filepond.css"` line auto-added to `globals.css` so the
styling kicks in immediately.
⚠ Still manual after install (one line each):
1. `<body className="squad-ui">` in `layout.tsx`. shadcn's CLI doesn't
modify JSX files, so this stays manual. The class is what makes
tokens like `--background` / `--foreground` / `--radius` cascade to
the rest of your app instead of just to Squad components.
2. Env vars for server routes. Blocks that ship a route handler also
ship a `.env.example` with the matching variable names — copy it to
`.env.local`, fill in the values, then restart `next dev`. Without
them the route 500s on every request.
3. Deployed registry lag. `https://sdk-components.thesqd.com/r/<name>.json`
is a CDN snapshot. New extras (CSS imports, routes) only show up
after the docs redeploy. If you're testing a brand-new addition,
point the CLI at the dev server URL or local registry JSON instead.
⚠ Environment gotchas (caught by a fresh-app smoke test):
4. `shadcn add` errors with `spawn yarn ENOENT`. shadcn's
package-manager detector walks up the directory tree looking for the
nearest lockfile. A stray `yarn.lock` in a parent directory (often
`~/yarn.lock`) makes it pick yarn even when your project uses npm.
Fix: run `npm install` in the project first so the app's own
`package-lock.json` becomes the nearest lockfile — or delete the
stray parent lockfile.
5. Next.js warns "multiple lockfiles" and infers your home dir as
the workspace root. Same root cause as #4 — a stray
`~/package-lock.json` outranks the app's. Either delete it or set
`turbopack.root` in `next.config.ts`:
import path from "node:path";
export default { turbopack: { root: path.resolve(".") } };
6. `next dev` runs Turbopack even with `--no-turbopack`. Next 16
defaults `next dev` to Turbopack regardless of the
`create-next-app` flag (which only affects scaffolding). Pass
`next dev --webpack` in your `package.json` dev script to opt
out, or just embrace Turbopack — it's stable for this stack.
7. Branded fonts (Albra + Inter) need the `/typography` setup.
`theme.json` ships only a self-referencing system-font fallback under
`.squad-ui` — if you don't wire `next/font` with the `--font-sans`
and `--font-heading` variables on `<html>`, you get system-ui (still
readable, but not branded). Follow the `/typography` guide to load
Inter via `next/font/google` and Albra via `next/font/local`. The
theme's fallback is intentionally a no-op when those vars are set
higher in the cascade, so you don't have to remove anything.TXTVite only
Vite-specific snags on top of the shared list above:
1. `shadcn init` can't resolve "@/..." — the CLI reads your path alias
from tsconfig. In a Vite scaffold the alias has to live in BOTH
`tsconfig.json` and `tsconfig.app.json` (the app config is where the
editor + build actually read `compilerOptions`). Add
`"baseUrl": "."` and `"paths": { "@/*": ["./src/*"] }` to each.
2. Imports work in the editor but break at build — you set the tsconfig
alias but forgot the matching `resolve.alias` in `vite.config.ts`.
tsconfig paths drive type-checking; Vite needs its own alias to
actually bundle them. See the "Project config" block above.
3. `@import "./styles/squad-ui.css"` 404s — the theme install writes the
token file relative to your `components.json` css path. In a Vite app
that's `src/index.css`, so the tokens land at `src/styles/squad-ui.css`
and the import is `./styles/squad-ui.css` (not `../styles/...` like Next).
4. Tailwind utilities don't apply — make sure `@tailwindcss/vite` is in
your `plugins` array in `vite.config.ts`. Vite uses the plugin, not a
PostCSS config, for Tailwind v4.
5. No web font / wrong type — there's no `next/font`. Either load Inter
via the `<link>` in `index.html` (shown above) or `@fontsource/inter`,
then point `--font-sans` / `--font-heading` at it under `.squad-ui`.
Skip it and you get the system-ui fallback (readable, not branded).Monorepos (Turborepo / pnpm workspaces)
Squad UI ships with no `target` paths — the CLI writes each file wherever your `components.json` aliases `ui` to. Same install command for single-app and monorepo setups; only the alias differs.
Squad UI items declare `path: "src/components/ui/<name>.tsx"` in their
manifests with **no `target`** — the shadcn CLI strips the path to its
basename and writes the file wherever `aliases.ui` resolves in your
`components.json`. That means monorepo (Turborepo / pnpm workspaces) and
single-app projects use the same install command — only the `components.json`
in the directory you run `npx shadcn add` from differs.
Standard single-app setup:
```json
{
"aliases": {
"ui": "@/components/ui",
"components": "@/components",
"lib": "@/lib",
"utils": "@/lib/utils",
"hooks": "@/hooks"
}
}
```
→ files land at `src/components/ui/<name>.tsx`.
Monorepo (Turborepo / pnpm) — put a `components.json` in `packages/ui/`:
```json
{
"aliases": {
"ui": "@workspace/ui/components",
"components": "@workspace/ui/components",
"lib": "@workspace/ui/lib",
"utils": "@workspace/ui/lib/utils",
"hooks": "@workspace/ui/hooks"
}
}
```
→ files land at `packages/ui/src/components/<name>.tsx` and their internal
imports are auto-rewritten from `@/components/ui/<dep>` →
`@workspace/ui/components/<dep>`. Run `npx shadcn@latest add …` from inside
`packages/ui/` (or from any workspace whose own `components.json` aliases `ui`
to the same shared path).
⚠ Pitfall — DO NOT include a trailing `/ui` in your `ui` alias:
```json
"ui": "@workspace/ui/components/ui" // ← results in components/ui/<name>.tsx, an extra dir level
"ui": "@workspace/ui/components" // ← correct
```
The shadcn CLI appends `<basename>.tsx` directly to whatever the alias resolves
to. Trailing `/ui` creates a duplicated subdirectory — you'll see imports break
because Squad's registry items resolve their inter-dependencies as
`@<alias>/<dep>`, not `@<alias>/ui/<dep>`.Browse the registry
Every published Squad UI item is indexed in a single JSON file at [`https://sdk-components.thesqd.com/r/registry.json`](https://sdk-components.thesqd.com/r/registry.json). Individual item manifests follow the pattern `https://sdk-components.thesqd.com/r/<name>.json` — point the shadcn CLI at any of them.
$Terminal
# Full registry index — names, types, files, registryDependencies for every item
curl https://sdk-components.thesqd.com/r/registry.json
# Pipe through jq for a quick item list
curl -s https://sdk-components.thesqd.com/r/registry.json | jq -r '.items[] | "\(.type)\t\(.name)"'Manual install
If you can't run the shadcn CLI, every item's manifest JSON is fetchable directly. Paste the file content from the manifest into the path declared in its `files[].path` field.
Every registry item ships its full source inside its manifest JSON.
Open the URL in a browser to inspect the file paths, the
`registryDependencies` (other registry items the CLI will install),
and the `dependencies` (npm packages the CLI will `npm install`).
Manifest URL pattern:
https://sdk-components.thesqd.com/r/<name>.json
For example:
- https://sdk-components.thesqd.com/r/theme.json
- https://sdk-components.thesqd.com/r/badge.json
- https://sdk-components.thesqd.com/r/kanban-board-showcase.json
The full registry index (every published item, with names, types, and
dependencies) is available as a single JSON file:
https://sdk-components.thesqd.com/r/registry.json
A human-readable index also lives at /llms.txt.