Skip to main content
The AdonisJS Starter Kit uses ShadCN UI - a collection of beautifully designed, accessible components built with Radix UI and Tailwind CSS.

Overview

ShadCN UI components are located in the packages/ui workspace:
packages/ui/
├── src/
│   ├── components/          # UI components
│   │   ├── button.tsx
│   │   ├── card.tsx
│   │   ├── input.tsx
│   │   └── ...
│   ├── hooks/              # Shared hooks
│   ├── lib/                # Utilities
│   │   └── utils.ts        # cn() helper
│   └── styles/
│       └── globals.css     # Global styles
├── components.json         # ShadCN config
└── package.json
Components are in a separate workspace package (@workspace/ui) and can be used across multiple apps.

Using Components

Import components from the @workspace/ui package:
import { Button } from '@workspace/ui/components/button'
import { Card, CardHeader, CardTitle, CardContent } from '@workspace/ui/components/card'
import { Input } from '@workspace/ui/components/input'

function MyComponent() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Welcome</CardTitle>
      </CardHeader>
      <CardContent>
        <Input placeholder="Enter your email" />
        <Button>Submit</Button>
      </CardContent>
    </Card>
  )
}

Available Components

The starter kit includes the following ShadCN components:

Button

Multiple variants and sizes

Card

Container with header and content

Input

Text input with variants

Textarea

Multi-line text input

Select

Dropdown selection

Dialog

Modal dialogs

Sheet

Side panels and drawers

Table

Data tables with sorting

Dropdown Menu

Context menus

Tooltip

Hover tooltips

Alert

Alert messages

Badge

Status badges

Avatar

User avatars

Checkbox

Checkbox inputs

Radio Group

Radio button groups

Progress

Progress bars

Skeleton

Loading skeletons

Toast

Toast notifications (Sonner)

Component Examples

Button Component

The Button component supports multiple variants and sizes:
import { Button } from '@workspace/ui/components/button'

// Variants
<Button variant="default">Default</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>

// Sizes
<Button size="default">Default</Button>
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>
<Button size="icon"><Icon /></Button>
The asChild prop allows you to render the button as a different element (like Link) while keeping button styles.

Card Component

The Card component is perfect for grouping related content:
import {
  Card,
  CardHeader,
  CardTitle,
  CardDescription,
  CardContent,
  CardFooter,
  CardAction,
} from '@workspace/ui/components/card'

function UserCard() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>User Profile</CardTitle>
        <CardDescription>Manage your account settings</CardDescription>
        <CardAction>
          <Button variant="ghost" size="icon-sm">
            <MoreIcon />
          </Button>
        </CardAction>
      </CardHeader>
      <CardContent>
        <p>Your profile information goes here</p>
      </CardContent>
      <CardFooter>
        <Button>Save Changes</Button>
      </CardFooter>
    </Card>
  )
}

Form Components

The starter kit includes custom form components with built-in error handling:
import { Input } from '@workspace/ui/components/input'
import { PasswordInput } from '@workspace/ui/components/password-input'
import {
  FieldSet,
  FieldGroup,
  Field,
  FieldLabel,
  FieldError,
  FieldSeparator,
} from '@workspace/ui/components/field'
import { FieldErrorBag } from '@workspace/ui/components/field-error-bag'

function MyForm({ errors }) {
  return (
    <FieldSet>
      <FieldGroup>
        <Field>
          <FieldLabel htmlFor="email">Email</FieldLabel>
          <Input
            id="email"
            type="email"
            placeholder="[email protected]"
          />
          <FieldErrorBag errors={errors} field="email" />
        </Field>
        
        <Field>
          <FieldLabel htmlFor="password">Password</FieldLabel>
          <PasswordInput id="password" />
          <FieldErrorBag errors={errors} field="password" />
        </Field>
        
        <FieldSeparator>OR</FieldSeparator>
        
        <Field orientation="responsive">
          <Button type="submit">Submit</Button>
        </Field>
      </FieldGroup>
    </FieldSet>
  )
}

Data Table Component

The starter kit includes a powerful DataTable component built with TanStack Table:
import { DataTable } from '@workspace/ui/components/data-table'
import { useDataTable } from '@workspace/ui/hooks/use-data-table'

const columns = [
  {
    accessorKey: 'name',
    header: 'Name',
  },
  {
    accessorKey: 'email',
    header: 'Email',
  },
]

function UsersTable({ data }) {
  const table = useDataTable({
    data,
    columns,
  })
  
  return <DataTable table={table} />
}

Adding New Components

The starter kit uses ShadCN’s CLI to add new components.
1

Navigate to UI package

cd packages/ui
2

Add component using npx

npx shadcn@latest add [component-name]
For example:
npx shadcn@latest add accordion
npx shadcn@latest add tabs
npx shadcn@latest add calendar
3

Component is added automatically

The component will be added to packages/ui/src/components/ and you can start using it immediately:
import { Accordion } from '@workspace/ui/components/accordion'
Always run the shadcn command from the packages/ui directory to ensure components are added to the correct location.

Component Configuration

The ShadCN configuration is in packages/ui/components.json:
packages/ui/components.json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "src/styles/globals.css",
    "baseColor": "neutral",
    "cssVariables": true
  },
  "iconLibrary": "lucide",
  "aliases": {
    "components": "@workspace/ui/components",
    "utils": "@workspace/ui/lib/utils",
    "hooks": "@workspace/ui/hooks",
    "lib": "@workspace/ui/lib",
    "ui": "@workspace/ui/components"
  }
}

Customizing Components

Modifying Existing Components

Since ShadCN components are copied into your project, you can modify them directly:
packages/ui/src/components/button.tsx
const buttonVariants = cva(
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-white hover:bg-destructive/90",
        outline: "border bg-background shadow-xs hover:bg-accent",
        // Add your custom variant
        custom: "bg-gradient-to-r from-purple-500 to-pink-500",
      },
      size: {
        default: "h-9 px-4 py-2",
        sm: "h-8 rounded-md gap-1.5 px-3",
        lg: "h-10 rounded-md px-6",
        // Add custom size
        xl: "h-12 rounded-lg px-8 text-lg",
      },
    },
  }
)

Creating Composite Components

Build higher-level components from primitives:
app/common/ui/components/user_avatar.tsx
import { Avatar, AvatarImage, AvatarFallback } from '@workspace/ui/components/avatar'
import { Tooltip, TooltipTrigger, TooltipContent } from '@workspace/ui/components/tooltip'

interface UserAvatarProps {
  user: {
    fullName: string
    avatarUrl?: string
  }
  showTooltip?: boolean
}

export function UserAvatar({ user, showTooltip = true }: UserAvatarProps) {
  const initials = user.fullName
    .split(' ')
    .map(n => n[0])
    .join('')
    .toUpperCase()
  
  const avatar = (
    <Avatar>
      {user.avatarUrl && <AvatarImage src={user.avatarUrl} alt={user.fullName} />}
      <AvatarFallback>{initials}</AvatarFallback>
    </Avatar>
  )
  
  if (!showTooltip) return avatar
  
  return (
    <Tooltip>
      <TooltipTrigger>{avatar}</TooltipTrigger>
      <TooltipContent>{user.fullName}</TooltipContent>
    </Tooltip>
  )
}

Theming

The starter kit uses CSS variables for theming:
packages/ui/src/styles/globals.css
@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 240 10% 3.9%;
    --primary: 240 5.9% 10%;
    --primary-foreground: 0 0% 98%;
    /* ... more variables */
  }
  
  .dark {
    --background: 240 10% 3.9%;
    --foreground: 0 0% 98%;
    --primary: 0 0% 98%;
    --primary-foreground: 240 5.9% 10%;
    /* ... more variables */
  }
}
The starter kit includes a theme switcher. Use the next-themes package via ThemeProvider for dark mode support.

Utility Functions

cn() Helper

The cn() utility combines Tailwind classes with class variance authority:
import { cn } from '@workspace/ui/lib/utils'

function MyComponent({ className, isActive }) {
  return (
    <div className={cn(
      "base-class",
      isActive && "active-class",
      className
    )}>
      Content
    </div>
  )
}

Icons

The starter kit uses Lucide React for icons:
import { Mail, Lock, User, Settings } from 'lucide-react'

function MyComponent() {
  return (
    <div>
      <Mail className="size-4" />
      <Lock className="size-6 text-red-500" />
      <User />
      <Settings className="animate-spin" />
    </div>
  )
}
Browse all available icons at lucide.dev

Best Practices

Create wrapper components with domain-specific names:
// Good
<UserActionButton onClick={deleteUser}>
  Delete User
</UserActionButton>

// Instead of
<Button variant="destructive" onClick={deleteUser}>
  Delete User
</Button>
ShadCN components are built with Radix UI and are accessible by default. Maintain this by:
  • Using proper ARIA labels
  • Including keyboard navigation
  • Testing with screen readers
Instead of modifying base components, create new composite components:
// Create a specialized button
export function DangerButton(props) {
  return <Button variant="destructive" {...props} />
}
Stick to the predefined variants and sizes to maintain consistency across your app.

Next Steps

Frontend Development

Learn about React and Inertia.js integration

ShadCN UI Docs

Browse the official ShadCN documentation

Build docs developers (and LLMs) love