Vibecoder UI Guide
Vibecoder UI Guide
Section titled “Vibecoder UI Guide”This guide is for vibecoders — contributors who want to improve the look and feel of Cosella’s UI without touching business logic, API calls, state management, or any functionality. If you follow this guide, you can safely make visual improvements, add new components, tweak styles, and enhance the user experience — all without breaking anything.
What You Can Touch
Section titled “What You Can Touch”| Area | Safe to Modify | Example |
|---|---|---|
| UI components | packages/ui/src/ | Button styles, Card layouts, Modal animations |
| Design tokens | packages/tokens/src/theme.css | Colors, spacing, typography, shadows |
| Component CSS | packages/tokens/src/components.css | .cs-btn-*, .cs-panel, .cs-badge-* |
| Storybook stories | packages/ui/src/**/*.stories.tsx | Adding stories for existing components |
| App page layouts | apps/*/src/features/*/components/ | Layout, spacing, alignment |
| Icons | Lucide React icons in components | Swapping or adding icons |
What You Must NOT Touch
Section titled “What You Must NOT Touch”| Area | Why | Files to Avoid |
|---|---|---|
| API client | Business logic, data fetching | packages/api-client/ |
| Domain types | Shared data contracts | packages/domain/ |
| Auth / Permissions | Security-critical | packages/auth/, packages/permissions/ |
| Realtime | WebSocket connections | packages/realtime/ |
| State management | Zustand stores, React Query hooks | **/hooks/, **/store.ts |
| Route definitions | Navigation logic | **/route.ts, routeTree.gen.ts |
| Test files for logic | Don’t modify existing tests | *.test.ts (unless yours) |
| Build config | Turborepo, Vite, TypeScript | turbo.json, vite.config.ts, tsconfig.json |
The Golden Rule
Section titled “The Golden Rule”If it imports from @cosella/domain, @cosella/auth, @cosella/realtime, or @cosella/api-client — don’t touch it.
If it only imports from @cosella/ui, @cosella/tokens, or uses Tailwind classes — you’re safe.
Quick Start: Your First UI Change
Section titled “Quick Start: Your First UI Change”Step 1: Set Up
Section titled “Step 1: Set Up”# Clone and installgit clone https://github.com/YOUR_USERNAME/cosella.gitcd cosellapnpm install
# Verify everything workspnpm typecheckStep 2: Start Storybook
Section titled “Step 2: Start Storybook”pnpm --filter @cosella/storybook devThis opens the component library at http://localhost:6006. Browse all existing components here.
Step 3: Make a Safe Change
Section titled “Step 3: Make a Safe Change”Pick one of these safe tasks:
- Change a button color in
packages/ui/src/button/Button.tsx - Adjust spacing in
packages/ui/src/card/Card.tsx - Add a new badge variant in
packages/ui/src/badge/Badge.tsx - Improve a Storybook story in any
*.stories.tsxfile
Step 4: Verify
Section titled “Step 4: Verify”# Type check passespnpm typecheck
# Lint passespnpm lint
# Stories still renderpnpm --filter @cosella/storybook devAnatomy of a UI Component
Section titled “Anatomy of a UI Component”Every component in packages/ui follows this structure:
packages/ui/src/└── button/ # kebab-case directory name ├── Button.tsx # Component implementation ├── Button.stories.tsx # Storybook stories └── Button.test.tsx # Vitest tests (optional for UI)Component Pattern
Section titled “Component Pattern”Here’s the pattern every component follows:
// 1. Imports — use cn() for class merging, cva for variantsimport { cva, type VariantProps } from 'class-variance-authority';import { Slot } from '@radix-ui/react-slot';import { cn } from '../lib/utils.js';
// 2. Variant definitions — CVA handles variant + defaultconst buttonVariants = cva( 'inline-flex items-center justify-center gap-2 rounded-lg font-medium', { variants: { variant: { primary: 'bg-primary text-white', outline: 'border border-border bg-transparent', }, size: { sm: 'h-9 px-3 text-sm', default: 'h-10 px-4 text-sm', }, }, defaultVariants: { variant: 'primary', size: 'default', }, },);
// 3. Props interface — extends HTML attributes + CVA variantsexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { asChild?: boolean;}
// 4. Component — uses cn() for className mergingexport function Button({ className, variant, size, asChild = false, children, ref, ...props}: ButtonProps & { ref?: React.Ref<HTMLButtonElement> }) { const Comp = asChild ? Slot : 'button'; return ( <Comp ref={ref} className={cn(buttonVariants({ variant, size }), className)} {...props} > {children} </Comp> );}Key Conventions
Section titled “Key Conventions”- Always use
cn()for className merging (never string concatenation) - Always use
cva()for variant-based styling - Always accept
classNameprop for consumer overrides - Always export the props interface alongside the component
- Always use
refas prop (React 19 — noforwardRef) - Always import from
../lib/utils.jsforcn()
Design Tokens: Your Color Palette
Section titled “Design Tokens: Your Color Palette”All colors, spacing, and typography are defined as CSS custom properties in packages/tokens/src/theme.css.
Color Tokens
Section titled “Color Tokens”Use these Tailwind classes (they map to the CSS variables):
// Backgrounds<div className="bg-bg-base" /> {/* Page background */}<div className="bg-bg-raised" /> {/* Card / panel */}<div className="bg-bg-subtle" /> {/* Muted surface */}
// Text<p className="text-text-primary" /> {/* Main text */}<p className="text-text-secondary" /> {/* Secondary text */}<p className="text-text-muted" /> {/* Muted text */}
// Brand<Button className="bg-primary" /> {/* Primary brand color */}<div className="border-border" /> {/* Standard border */}
// Status<Alert className="bg-success/10 border-success/25 text-success" /><Alert className="bg-warning/10 border-warning/25 text-warning" /><Alert className="bg-danger/10 border-danger/25 text-danger" /><Alert className="bg-info/10 border-info/25 text-info" />Typography
Section titled “Typography”<h1 className="text-2xl font-bold text-text-primary" /> {/* Page title */}<h2 className="text-lg font-bold text-text-primary" /> {/* Section title */}<h3 className="text-base font-bold text-text-primary" /> {/* Card title */}<p className="text-sm text-text-muted" /> {/* Body text */}<label className="text-sm font-medium text-text-primary" /> {/* Label */}Spacing & Layout
Section titled “Spacing & Layout”{/* Standard page layout */}<div className="cs-page-shell"> <div className="cs-page-stack"> <div className="cs-page-header">...</div> <div className="cs-panel">...</div> </div></div>
{/* Card with header, content, footer */}<Card> <CardHeader><CardTitle>Title</CardTitle></CardHeader> <CardContent>Content</CardContent> <CardFooter>Actions</CardFooter></Card>Adding a New Component
Section titled “Adding a New Component”Step 1: Run the Generator
Section titled “Step 1: Run the Generator”pnpm gen componentEnter a PascalCase name (e.g., ProgressBar). This creates:
packages/ui/src/progress-bar/ProgressBar.tsxpackages/ui/src/progress-bar/ProgressBar.test.tsx- Export added to
packages/ui/src/index.ts
Step 2: Implement the Component
Section titled “Step 2: Implement the Component”Replace the template code with your implementation:
import { cn } from '../lib/utils.js';
export interface ProgressBarProps extends React.HTMLAttributes<HTMLDivElement> { value: number; max?: number; variant?: 'primary' | 'success' | 'warning' | 'danger';}
export function ProgressBar({ className, value, max = 100, variant = 'primary', ...props}: ProgressBarProps) { const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
const variantStyles = { primary: 'bg-primary', success: 'bg-success', warning: 'bg-warning', danger: 'bg-danger', };
return ( <div className={cn('h-2 w-full rounded-full bg-bg-subtle', className)} role="progressbar" aria-valuenow={value} aria-valuemin={0} aria-valuemax={max} {...props} > <div className={cn('h-full rounded-full transition-all', variantStyles[variant])} style={{ width: `${percentage}%` }} /> </div> );}Step 3: Add a Storybook Story
Section titled “Step 3: Add a Storybook Story”Create ProgressBar.stories.tsx:
import type { Meta, StoryObj } from '@storybook/react';import { ProgressBar } from './ProgressBar.js';
const meta = { title: 'Primitives/ProgressBar', component: ProgressBar, tags: ['autodocs'], argTypes: { value: { control: { type: 'range', min: 0, max: 100 } }, variant: { control: 'select', options: ['primary', 'success', 'warning', 'danger'] }, },} satisfies Meta<typeof ProgressBar>;
export default meta;type Story = StoryObj<typeof meta>;
export const Default: Story = { args: { value: 60 } };export const Complete: Story = { args: { value: 100, variant: 'success' } };export const Warning: Story = { args: { value: 30, variant: 'warning' } };export const Danger: Story = { args: { value: 10, variant: 'danger' } };Step 4: Verify
Section titled “Step 4: Verify”# Storybook renders your new componentpnpm --filter @cosella/storybook dev
# Type check passespnpm typecheck
# Lint passespnpm lintCommon Vibecoder Tasks
Section titled “Common Vibecoder Tasks”Task: Change Button Colors
Section titled “Task: Change Button Colors”File: packages/ui/src/button/Button.tsx
// Beforeprimary: 'bg-primary text-white hover:opacity-90',
// After — use a different tokenprimary: 'bg-brand-500 text-white hover:bg-brand-600',Task: Adjust Card Spacing
Section titled “Task: Adjust Card Spacing”File: packages/ui/src/card/Card.tsx
// Before<div className={cn('px-5 py-4', className)} {...props} />
// After — more padding<div className={cn('px-6 py-5', className)} {...props} />Task: Add a New Badge Variant
Section titled “Task: Add a New Badge Variant”File: packages/ui/src/badge/Badge.tsx
Add to the variant styles:
const badgeVariants = cva('...', { variants: { variant: { // ... existing variants teal: 'bg-badge-teal-bg text-badge-teal-text', // NEW }, },});Then add the token in packages/tokens/src/theme.css:
--color-badge-teal-bg: oklch(97% 0.04 170);--color-badge-teal-text: oklch(44% 0.13 170);Task: Improve a Storybook Story
Section titled “Task: Improve a Storybook Story”File: Any *.stories.tsx
Add interaction tests:
export const InteractionClick: Story = { args: { children: 'Click me', onClick: fn() }, play: async ({ args, canvasElement, step }) => { const canvas = within(canvasElement); const btn = canvas.getByRole('button', { name: /click me/i });
await step('button is clickable', async () => { await userEvent.click(btn); await expect(args.onClick).toHaveBeenCalledTimes(1); }); },};Task: Add a Loading Skeleton
Section titled “Task: Add a Loading Skeleton”File: packages/ui/src/skeleton/Skeleton.tsx
Add a new skeleton variant:
export function SkeletonChart({ className }: SkeletonProps) { return ( <div className={cn('h-48 w-full rounded-lg bg-bg-subtle animate-pulse', className)} /> );}Verification Checklist
Section titled “Verification Checklist”Before submitting your PR, run these commands:
# 1. Type check — must passpnpm typecheck
# 2. Lint — must passpnpm lint
# 3. Storybook — your component renders correctlypnpm --filter @cosella/storybook dev
# 4. Visual check — compare with existing components# Open Storybook and check your component looks consistentCommit Convention
Section titled “Commit Convention”Follow Conventional Commits:
# Goodgit commit -m "feat(ui): add ProgressBar component"git commit -m "style(ui): adjust Button hover states"git commit -m "docs(storybook): add interaction tests for Alert"
# Badgit commit -m "update button"git commit -m "fixed styles"Getting Stuck?
Section titled “Getting Stuck?”- Check Storybook —
pnpm --filter @cosella/storybook dev - Check existing components —
packages/ui/src/for patterns - Check design tokens —
packages/tokens/src/theme.cssfor available colors - Check the cn() utility —
packages/ui/src/lib/utils.ts
What NOT to Do
Section titled “What NOT to Do”- Don’t import from
@cosella/domain,@cosella/auth,@cosella/realtime, or@cosella/api-client - Don’t modify route definitions or
routeTree.gen.ts - Don’t change Zustand stores or React Query hooks
- Don’t modify existing test files (
.test.ts) unless you wrote them - Don’t edit
turbo.json,vite.config.ts, ortsconfig.json - Don’t use
ascasts oranytypes - Don’t use
forwardRef— React 19 usesrefas prop
Summary
Section titled “Summary”| What | Where | Safe? |
|---|---|---|
| Component styles | packages/ui/src/*/ | Yes |
| Design tokens | packages/tokens/src/theme.css | Yes |
| Storybook stories | packages/ui/src/**/*.stories.tsx | Yes |
| App page layouts | apps/*/src/features/*/components/ | Yes |
| Icons | Lucide React | Yes |
| API calls | packages/api-client/ | No |
| Domain types | packages/domain/ | No |
| Auth logic | packages/auth/ | No |
| State stores | Zustand/React Query | No |
| Route definitions | TanStack Router files | No |