Components Overview
Sunday uses a component-based architecture with Radix UI primitives and Tailwind CSS.
Component Organization
components/
├── ui/ # Base UI components
│ ├── button.tsx
│ ├── dialog.tsx
│ ├── input.tsx
│ └── ...
├── board/ # Board-specific components
│ ├── table-view.tsx
│ ├── kanban-view.tsx
│ └── ...
├── layout/ # Layout components
│ ├── sidebar.tsx
│ ├── header.tsx
│ └── ...
├── automations/ # Automation panel
├── filters/ # Filter components
├── forms/ # Form components
├── search/ # Search components
└── landing/ # Landing page componentsDesign System
Colors
Primary colors use Tailwind's color palette:
| Purpose | Class | Example |
|---|---|---|
| Primary | blue-600 | Buttons, links |
| Success | green-500 | Done status |
| Warning | yellow-500 | Review status |
| Error | red-500 | Stuck status |
| Neutral | slate-* | Text, borders |
Typography
- Headings:
font-semiboldorfont-bold - Body: Default font weight
- Monospace: Code blocks, IDs
Spacing
Uses Tailwind's spacing scale (4px base):
p-2= 8pxp-4= 16pxgap-4= 16px
Shadows
shadow-sm - Subtle elevation
shadow - Cards, modals
shadow-lg - Dropdowns
shadow-xl - PopoversComponent Patterns
Composable Components
Components are designed to be composable:
<Dialog>
<DialogTrigger>
<Button>Open</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Title</DialogTitle>
</DialogHeader>
{/* Content */}
</DialogContent>
</Dialog>Controlled vs Uncontrolled
Most components support both modes:
// Uncontrolled (internal state)
<Checkbox />
// Controlled
<Checkbox checked={checked} onCheckedChange={setChecked} />Forwarding Refs
Components forward refs for DOM access:
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, ...props }, ref) => (
<button ref={ref} className={className} {...props} />
)
)Radix UI Integration
Base UI components wrap Radix primitives:
import * as DialogPrimitive from '@radix-ui/react-dialog'
export const Dialog = DialogPrimitive.Root
export const DialogTrigger = DialogPrimitive.Trigger
export const DialogContent = forwardRef(({ className, ...props }, ref) => (
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay className="fixed inset-0 bg-black/50" />
<DialogPrimitive.Content
ref={ref}
className={cn("fixed left-1/2 top-1/2 ...", className)}
{...props}
/>
</DialogPrimitive.Portal>
))Styling with Tailwind
Class Variance Authority
Use CVA for component variants:
import { cva } from 'class-variance-authority'
const buttonVariants = cva(
"inline-flex items-center rounded-md font-medium",
{
variants: {
variant: {
default: "bg-blue-600 text-white hover:bg-blue-700",
secondary: "bg-slate-100 text-slate-900 hover:bg-slate-200",
ghost: "hover:bg-slate-100",
},
size: {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4",
lg: "h-12 px-6 text-lg",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
}
)cn() Utility
Merge Tailwind classes safely:
import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs) {
return twMerge(clsx(inputs))
}
// Usage
<div className={cn(
"base-classes",
condition && "conditional-class",
className
)} />