Kit includes 22 pre-configured shadcn/ui components built on Radix UI primitives with Class Variance Authority (CVA) for type-safe variant management and Tailwind CSS for styling. Components live in a shared
packages/ui/ package and are imported through scoped entry points to prevent cross-app leakage.This page covers the component architecture, scoped imports, available components, and how to add new ones. For color theming, see Color Themes & Dark Mode. For Tailwind configuration, see Customization.
Component Architecture
The component system is configured through
components.json, which tells the shadcn/ui CLI how to install new components:components.json — shadcn/ui Configuration
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
Key configuration choices:
style: "new-york"— Uses the New York variant of shadcn/ui (more opinionated, slightly different default styling than the "default" style)rsc: true— Components are compatible with React Server Components by defaultcssVariables: true— Colors reference CSS custom properties (the theme system) instead of hardcoded Tailwind colorsbaseColor: "neutral"— Neutral gray scale as the foundation color
How Components Work
Every component follows the same pattern: a Radix UI primitive provides accessible, unstyled behavior, CVA defines variant classes, and Tailwind applies the actual styles. The
cn() utility (from @nextsaas/utils) merges classes with proper conflict resolution via tailwind-merge.Radix UI Primitive (accessibility, keyboard nav, ARIA)
|
v
CVA Variants (variant="default" | "destructive" | "outline", size="sm" | "default" | "lg")
|
v
Tailwind Classes (bg-primary, text-sm, h-10, px-6, ...)
|
v
cn() merge (className prop overrides + tailwind-merge conflict resolution)
Scoped Imports
Components are organized into three scopes to prevent boilerplate-specific components from leaking into the marketing site and vice versa:
| Import Path | Contents | Used By |
|---|---|---|
@nextsaas/ui/shared | Shared components only | Any app |
@nextsaas/ui/boilerplate | Shared + boilerplate-only | apps/boilerplate/ |
@nextsaas/ui/marketing | Shared + marketing-only | apps/marketing/ |
The shared scope exports the foundational components used by all applications:
packages/ui/src/shared-index.ts — Shared Exports
/**
* @nextsaas/ui - Shared Components
*
* Components available to ALL applications in the monorepo.
* These are foundational UI elements used across both boilerplate and marketing apps.
*
* @scope shared
*/
// Core Form Components
export * from './button'
export * from './input'
export * from './label'
export * from './select'
export * from './textarea'
// Layout Components
export * from './card'
export * from './sheet'
// Feedback Components
export * from './badge'
export * from './sonner'
// Theme Components
export * from './theme-toggle'
// Internationalization Components
export * from './flag-icon'
Import from
@nextsaas/ui/boilerplate (not @nextsaas/ui or direct file paths). Scoped imports ensure you only pull in components intended for your app, keeping bundle sizes small and preventing accidental cross-app dependencies.Component Categories
Shared Components
Available to all applications via
@nextsaas/ui/shared (also included in boilerplate and marketing scopes):| Component | File | Description |
|---|---|---|
| Button | button.tsx | Primary action element with 8 variants and 5 sizes |
| Input | input.tsx | Text input with consistent border and focus styles |
| Label | label.tsx | Form label with disabled state support |
| Select | select.tsx | Dropdown select with Radix primitives |
| Textarea | textarea.tsx | Multi-line text input |
| Card | card.tsx | Container with header, content, footer slots |
| Sheet | sheet.tsx | Slide-out panel (mobile nav, sidebars) |
| Badge | badge.tsx | Status indicator with variant colors |
| Sonner | sonner.tsx | Toast notification system |
| ThemeToggle | theme-toggle.tsx | Dark/light mode switch |
| FlagIcon | flag-icon.tsx | Country flag icons for i18n |
Boilerplate-Only Components
Additional components available via
@nextsaas/ui/boilerplate:packages/ui/src/boilerplate-index.ts — Boilerplate Scope
// ============================================================================
// Alert Components (error messages, notifications in app)
export * from './alert'
export * from './alert-dialog'
// Form Utilities (react-hook-form integration)
export * from './form'
// Modal Components (app modals, confirmations)
export * from './dialog'
// Navigation Components (user menu in dashboard)
export * from './dropdown-menu'
// Progress & Loading Components
export * from './progress'
export * from './skeleton'
// Toast Notifications (in-app feedback)
export * from './toast'
export * from './use-toast'
// Note: toaster.tsx available but not exported to avoid conflict with sonner
| Component | Description |
|---|---|
| Alert / AlertDialog | Error messages, confirmation dialogs |
| Form | react-hook-form integration with Zod validation |
| Dialog | Modal windows for in-app interactions |
| DropdownMenu | User menu, action menus in dashboard |
| Progress | Loading bars, upload progress indicators |
| Skeleton | Loading placeholder animations |
| Toast / useToast | Legacy toast system (Sonner preferred for new code) |
Button Deep-Dive
The Button component demonstrates the CVA pattern used across all components:
packages/ui/src/button.tsx — Button with CVA Variants
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@nextsaas/utils'
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
default: 'bg-primary text-background dark:text-foreground/90 hover:text-white',
destructive: 'bg-red-600 text-white hover:bg-red-700 dark:bg-red-600 dark:hover:bg-red-700',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/90',
accent: 'bg-accent text-accent-foreground hover:bg-accent/90',
uncolored: 'bg-card text-card-muted-foreground hover:text-card-foreground',
outline: 'border hover:border-foreground/75',
ghost: 'border border-transparent bg-primary bg-clip-text text-transparent hover:border-foreground/75 hover:text-foreground',
link: 'bg-primary bg-clip-text text-transparent underline-offset-8 hover:text-foreground inline flex-none !px-0 !h-auto !w-auto',
},
size: {
sm: 'h-9 px-5 text-xs',
default: 'h-10 px-6 text-sm',
md: 'h-11 px-7 text-base',
lg: 'h-12 px-8 text-base',
xl: 'h-14 px-11 text-lg',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button'
// Ensure data-testid is passed through properly
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = 'Button'
export { Button, buttonVariants }
Variant Reference
| Variant | Appearance | Use Case |
|---|---|---|
default | Solid primary background | Primary actions (Submit, Save, Create) |
destructive | Red background | Dangerous actions (Delete, Remove) |
secondary | Muted background | Secondary actions (Cancel, Back) |
accent | Accent background | Highlighted secondary actions |
outline | Border only | Tertiary actions, toggles |
ghost | Gradient text, border on hover | Subtle actions, navigation links |
link | Gradient text, underline offset | Inline text links |
uncolored | Card background | Neutral actions in card contexts |
Usage Examples
tsx
import { Button } from '@nextsaas/ui/boilerplate'
// Primary action
<Button>Save Changes</Button>
// Destructive with size
<Button variant="destructive" size="sm">Delete Account</Button>
// As a link (renders <a> instead of <button>)
<Button asChild variant="link">
<a href="/docs">Read the Docs</a>
</Button>
// Icon button
<Button variant="ghost" size="icon">
<SearchIcon />
</Button>
The
asChild prop uses Radix's Slot component to merge the Button's styling onto the child element — useful for rendering styled <a> tags or Next.js <Link> components.Card Pattern
The Card component uses a compound component pattern with separate sub-components for each section:
packages/ui/src/card.tsx — Card Component
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'rounded-xl border bg-card text-card-foreground shadow',
className
)}
{...props}
/>
))
Card.displayName = 'Card'
Each part (Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter) is a
forwardRef component with cn() for class merging. Use them together:tsx
import { Card, CardHeader, CardTitle, CardContent } from '@nextsaas/ui/boilerplate'
<Card>
<CardHeader>
<CardTitle>Monthly Usage</CardTitle>
</CardHeader>
<CardContent>
<p>You have used 1,234 of 5,000 credits this month.</p>
</CardContent>
</Card>
Adding New Components
1
Install the component
Use the shadcn/ui CLI to scaffold a new component. Run this from the boilerplate app directory:
bash
cd apps/boilerplate
npx shadcn@latest add accordion
This generates the component file in the configured
ui alias path.2
Move to the packages/ui directory
Move the generated component from the local
components/ui/ directory to packages/ui/src/:bash
mv src/components/ui/accordion.tsx ../../packages/ui/src/accordion.tsx
3
Update the cn import
The generated component imports
cn from the local path. Update it to use the shared package:typescript
// Before (generated by shadcn/ui CLI)
import { cn } from '@/lib/utils'
// After (monorepo shared utility)
import { cn } from '@nextsaas/utils'
4
Register in the scope
Export the component from the appropriate scope index file. For most boilerplate components, add it to
packages/ui/src/boilerplate-index.ts:typescript
// Accordion Component
export * from './accordion'
For components used across all apps, add to
shared-index.ts instead.The shadcn/ui CLI generates components with
import { cn } from '@/lib/utils'. This path doesn't exist in the packages/ui/ package. Always change it to import { cn } from '@nextsaas/utils' after moving the component.Project Components
Beyond the shared
packages/ui/ components, the boilerplate app has its own components organized by feature:src/components/
├── about/ # About page sections (hero, team, values, timeline)
├── ai/ # AI chat interface (chat, messages, input, RAG/LLM)
├── auth/ # Authentication (login forms, user button)
├── blog/ # Blog UI (sidebar, tags, like/share buttons)
├── contact/ # Contact page (hero, info, form)
├── credits/ # Credit system (low balance banner, usage counter)
├── dashboard/ # Dashboard UI (sidebar, header, analytics, subscriptions)
├── demo/ # Demo mode (nav item, demo provider)
├── forms/ # Form components (contact form)
├── home/ # Landing page (hero, features, newsletter)
├── layout/ # App layout (navbar, footer)
├── modals/ # Modal dialogs (credits exhausted)
├── payments/ # Payment UI (plan change dialog, pricing error)
├── shared/ # Cross-cutting (hero background)
├── test/ # Test utilities (no-clerk script, config error)
└── upload/ # File upload (drop zone, preview, hooks)
These are app-specific components — they import from
@nextsaas/ui/boilerplate for base UI elements and add business logic on top.Key Files
| File | Purpose |
|---|---|
components.json | shadcn/ui CLI configuration (style, paths, aliases) |
packages/ui/src/shared-index.ts | Shared component exports (11 components) |
packages/ui/src/boilerplate-index.ts | Boilerplate scope exports (shared + 8 additional) |
packages/ui/src/marketing-index.ts | Marketing scope exports (shared + navigation) |
packages/ui/src/button.tsx | Button component with 8 CVA variants |
packages/ui/src/card.tsx | Card compound component (6 sub-components) |
packages/utils/src/index.ts | cn() utility (clsx + tailwind-merge) |