Setup

Connect your portal to the SD design system registry. Step-by-step, tested, no guessing.

Setup

Connect your portal to the SD registry. Five steps, then every component is one command away.

Step 1: Install Dependencies

SD components use Radix UI primitives via the radix-ui package. Install it:

pnpm add radix-ui

You can keep your existing @radix-ui/react-* packages — both work simultaneously. New components pulled from the registry will use radix-ui.

Step 2: Add components.json

Create components.json in your portal's root directory. This tells the CLI where your project is structured and where to find the SD registry.

{
  "$schema": "https://ui.styledriven.com/schema.json",
  "style": "radix-sd",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "css": "app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "iconLibrary": "lucide",
  "registries": {
    "@sd": "http://localhost:4000/r/styles/radix-sd/{name}.json"
  }
}

When deploying, change the registry URL to production:

{
  "registries": {
    "@sd": "https://ui.styledriven.com/r/styles/radix-sd/{name}.json"
  }
}

Step 3: Add Registry CSS Variables

SD components use semantic color tokens like bg-primary, text-muted-foreground, border-border. Your portal needs to define what these resolve to.

Add this to your globals.css @theme block:

@theme {
  /* ... your existing SD brand tokens ... */
 
  /* Registry compatibility */
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --color-sidebar: var(--sidebar);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);
  --radius-sm: calc(var(--radius) * 0.6);
  --radius-md: calc(var(--radius) * 0.8);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) * 1.4);
  --radius-2xl: calc(var(--radius) * 1.8);
}

Then add the actual values in your :root and .dark blocks. These map shadcn's semantic tokens to SD brand values:

:root {
  --radius: 0.5rem;
 
  /* ... your existing --brand, --on-brand, etc. ... */
 
  /* Registry semantic tokens mapped to SD brand */
  --card: var(--background);
  --card-foreground: var(--foreground);
  --popover: var(--background);
  --popover-foreground: var(--foreground);
  --primary: var(--brand);
  --primary-foreground: var(--on-brand);
  --secondary: #f0f0f0;
  --secondary-foreground: var(--foreground);
  --muted: #f5f5f0;
  --muted-foreground: var(--gray);
  --accent-foreground: var(--foreground);
  --destructive: var(--accent);
  --destructive-foreground: #ffffff;
  --border: color-mix(in srgb, var(--foreground) 15%, transparent);
  --input: color-mix(in srgb, var(--foreground) 15%, transparent);
  --ring: var(--brand);
  --sidebar: #fafafa;
  --sidebar-foreground: var(--foreground);
  --sidebar-primary: var(--brand);
  --sidebar-primary-foreground: var(--on-brand);
  --sidebar-accent: #f5f5f0;
  --sidebar-accent-foreground: var(--foreground);
  --sidebar-border: color-mix(in srgb, var(--foreground) 10%, transparent);
  --sidebar-ring: var(--brand);
}
 
.dark {
  /* ... your existing dark mode --brand, --on-brand, etc. ... */
 
  /* Registry semantic tokens — dark mode */
  --card: #1a1a1a;
  --card-foreground: #ffffff;
  --popover: #1a1a1a;
  --popover-foreground: #ffffff;
  --primary: var(--brand);
  --primary-foreground: var(--on-brand);
  --secondary: #1f1f1f;
  --secondary-foreground: #ffffff;
  --muted: #1f1f1f;
  --muted-foreground: var(--gray);
  --accent-foreground: #ffffff;
  --destructive: var(--accent);
  --destructive-foreground: #ffffff;
  --border: color-mix(in srgb, #ffffff 10%, transparent);
  --input: color-mix(in srgb, #ffffff 15%, transparent);
  --ring: var(--brand);
  --sidebar: #1a1a1a;
  --sidebar-foreground: #ffffff;
  --sidebar-primary: var(--brand);
  --sidebar-primary-foreground: var(--on-brand);
  --sidebar-accent: #1f1f1f;
  --sidebar-accent-foreground: #ffffff;
  --sidebar-border: color-mix(in srgb, #ffffff 10%, transparent);
  --sidebar-ring: var(--brand);
}

The key mapping: --primary points to --brand, --destructive points to --accent (red), --muted-foreground points to --gray. This is how SD brand colors flow through to every registry component.

Step 4: Set the Registry URL

For local development, the SD registry serves from localhost:4000. Set this env var so dependency resolution works:

REGISTRY_URL=http://localhost:4000/r

Add it to your .env.local:

REGISTRY_URL=http://localhost:4000/r

Make sure the sd-ui dev server is running (pnpm dev in the sd-ui directory).

Step 5: Pull Components

Always use the @sd/ prefix to pull from the SD registry:

# Single component
REGISTRY_URL=http://localhost:4000/r npx shadcn@latest add @sd/button
 
# Multiple components
REGISTRY_URL=http://localhost:4000/r npx shadcn@latest add @sd/badge @sd/card @sd/dialog
 
# Custom SD components (not in upstream shadcn)
REGISTRY_URL=http://localhost:4000/r npx shadcn@latest add @sd/date-picker @sd/phone-input

Components land in components/ui/. You own the code.

The @sd/ prefix is required — without it, the CLI looks in the default shadcn registry which doesn't have the SD style.

Using It

import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { DatePicker } from "@/components/ui/date-picker"
 
export function Example() {
  return (
    <div>
      <Button>Get Started</Button>
      <Badge variant="destructive">Urgent</Badge>
      <DatePicker value={date} onChange={setDate} />
    </div>
  )
}

MCP Server

The registry supports MCP. Claude Code, Cursor, and VS Code can add components through the MCP server:

pnpm dlx shadcn@latest mcp init --client claude

After setup, Claude can search and add components conversationally.

Updating Components

To see what changed upstream before updating:

pnpm dlx shadcn@latest add @sd/button --dry-run
npx shadcn@latest add @sd/button --diff button.tsx

To overwrite with the latest version:

REGISTRY_URL=http://localhost:4000/r npx shadcn@latest add @sd/button --overwrite

Tech Stack

Every portal runs the same stack. SD components are built for this:

ToolVersion
Next.js16
React19
Tailwind CSSv4
Radix UILatest
TypeScript5.x
class-variance-authorityLatest

Troubleshooting

"Item was not found" error: Make sure you're using the @sd/ prefix and the sd-ui dev server is running on port 4000. Set REGISTRY_URL=http://localhost:4000/r before the command.

Components render unstyled: Your globals.css is missing the semantic CSS variables. Add the --primary, --muted, --border etc. mappings from Step 3.

Import error for radix-ui: Run npm install radix-ui in your portal. Registry components use the monolithic package.