Guide: Creating New Components¶
Overview¶
This guide provides step-by-step instructions for creating new components that follow the standardized color system and design patterns established in the design system.
Prerequisites¶
Before creating a new component, ensure you understand: - ✅ Color system standards (6-value opacity scale, 400/500 shades) - ✅ 3-design-color system (Primary, Indigo+Purple, Blue) - ✅ Focus state requirements - ✅ Dark mode patterns - ✅ Accessibility requirements
Reference: See the Colors Styling Guide for details.
Step-by-Step Process¶
Step 1: Determine Component Type¶
Identify which category your component belongs to:
- Button: Primary action, secondary action, accent, text, icon, toggle
- Card: Content container, clickable card
- Input: Text input, search input, textarea
- Badge: Status badge, AI badge, disclaimer badge
- Header: Page header, section header, secondary header
- Other: Empty state, message, loading indicator, etc.
Action: Choose the appropriate pattern from existing components.
Reference: See Component Colors Cheat Sheet for examples.
Step 2: Choose Colors¶
Design Colors (Use for backgrounds, gradients)¶
-
Primary: Use
primaryCSS variable for brand elements -
Indigo + Purple: Use
indigo-400/500+purple-400/500for gradients -
Blue: Use
blue-400/500for background gradients
Semantic Colors (Use for states)¶
- Success:
successorgreen-* - Error:
errororred-* - Warning:
warningoramber-* - Info:
infoorblue-*
Neutral Colors (Use for borders, text)¶
- Borders:
slate-200/50(light) orslate-800/50(dark) - Text:
foregroundormuted-foreground - Backgrounds:
slate-50(light) orslate-900(dark)
Action: Select colors based on component purpose.
Step 3: Apply Opacity Scale¶
Use only these 6 values:
| Opacity | Value | Usage |
|---|---|---|
/10 |
10% | Very subtle overlays |
/15 |
15% | Subtle backgrounds |
/20 |
20% | Light backgrounds |
/30 |
30% | Medium backgrounds (most common) |
/50 |
50% | Hover overlays |
/80 |
80% | Strong overlays |
Action: Choose appropriate opacity based on visual hierarchy.
Reference: See the Colors Styling Guide - Opacity Scale section.
Step 4: Add Dark Mode Support¶
Pattern: Always include both light and dark mode classes
// Light mode: 400 shades
"from-blue-400/30 via-indigo-400/20 to-purple-400/20"
// Dark mode: 500 shades
"dark:from-blue-500/30 dark:via-indigo-500/20 dark:to-purple-500/20"
Action: Add dark: variants for all color classes.
Reference: See the Colors Styling Guide - Dark Mode Guidelines section.
Step 5: Add Focus State¶
Required Pattern: All interactive elements must have focus states
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
Action: Add focus state to all interactive elements.
Reference: See the Colors Styling Guide - Accessibility Guidelines section.
Step 6: Add Hover State¶
Pattern: Use overlay or gradient changes
Buttons:
Cards:
Action: Add appropriate hover state.
Step 7: Verify Touch Targets¶
Requirement: Minimum 44x44px for all interactive elements
Action: Ensure all interactive elements meet minimum size.
Step 8: Add TypeScript Return Types¶
Requirement: All component functions must have explicit return types
Pattern: Use React.JSX.Element for components that return JSX
export function NewButton({
children,
onClick,
className,
disabled = false,
type = "button",
}: NewButtonProps): React.JSX.Element {
// Component implementation
}
For components that can return null:
export function OptionalComponent({
show,
}: OptionalComponentProps): React.JSX.Element | null {
if (!show) return null;
// Component implementation
}
For async functions:
For event handlers:
Action: Add explicit return types to all functions.
Reference: See ESLint rule @typescript-eslint/explicit-function-return-type
Step 9: Test Accessibility¶
- Contrast: Verify text/background contrast (4.5:1 minimum)
- Focus: Test keyboard navigation
- Colorblind: Verify state changes use ring/border in addition to color
- Touch Targets: Verify minimum 44x44px
Action: Test all accessibility requirements.
Component Templates¶
Template 1: Button Component¶
"use client";
import React from 'react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
export interface NewButtonProps {
children: React.ReactNode;
onClick?: () => void;
className?: string;
disabled?: boolean;
type?: "button" | "submit" | "reset";
}
export function NewButton({
children,
onClick,
className,
disabled = false,
type = "button",
}: NewButtonProps): React.JSX.Element {
return (
<Button
type={type}
onClick={onClick}
disabled={disabled}
className={cn(
// Size - ensure minimum 44x44px
"h-11 px-6 rounded-xl",
// Background - use appropriate gradient
"bg-gradient-to-br from-blue-400/30 via-indigo-400/30 via-purple-400/30 to-purple-400/20",
"dark:bg-gradient-to-br dark:from-blue-500/30 dark:via-indigo-500/30 dark:via-purple-500/30 dark:to-purple-500/20",
// Border
"border border-slate-200/50 dark:border-slate-800/50",
// Text
"text-foreground dark:text-white",
// Hover overlay
"hover:bg-white/50 dark:hover:bg-black/50",
// Focus state (required)
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2",
// Transitions
"transition-all duration-300",
// Disabled state
disabled && "opacity-50 cursor-not-allowed",
className
)}
>
{children}
</Button>
);
}
Template 2: Card Component¶
"use client";
import React from 'react';
import { cn } from '@/lib/utils';
export interface NewCardProps {
children: React.ReactNode;
onClick?: () => void;
className?: string;
clickable?: boolean;
}
export function NewCard({
children,
onClick,
className,
clickable = true,
}: NewCardProps) {
return (
<div
className={cn(
"group relative overflow-hidden",
// Background gradient
"bg-gradient-to-br from-blue-400/30 via-indigo-400/20 via-purple-400/20 to-purple-400/15",
"dark:bg-gradient-to-br dark:from-blue-500/30 dark:via-indigo-500/20 dark:via-purple-500/20 dark:to-purple-500/15",
// Border
"border border-slate-200/50 dark:border-slate-800/50",
// Hover effects
"hover:border-primary/30 hover:shadow-2xl hover:shadow-primary/10",
// Focus state (required for clickable)
clickable && "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2",
// Transitions
"transition-[transform,shadow,border-color] duration-300 ease-out",
// Clickable effects
clickable && "cursor-pointer hover:scale-[1.03] hover:-translate-y-1",
// Layout
"rounded-2xl p-6",
className
)}
onClick={clickable && onClick ? onClick : undefined}
tabIndex={clickable ? 0 : undefined}
>
{children}
</div>
);
}
Template 3: Input Component¶
"use client";
import React from 'react';
import { Input } from '@/components/ui/input';
import { cn } from '@/lib/utils';
export interface NewInputProps extends React.ComponentProps<typeof Input> {
className?: string;
}
export const NewInput = React.forwardRef<HTMLInputElement, NewInputProps>(
({ className, ...props }, ref) => {
return (
<div className="relative group">
{/* Glow effect on focus */}
<div className={cn(
"absolute inset-0 rounded-2xl opacity-0 group-focus-within:opacity-100 transition-opacity duration-500 blur-2xl -z-10",
"bg-gradient-to-r from-primary/20 via-indigo-400/20 to-purple-400/20",
"dark:from-primary/20 dark:via-indigo-500/20 dark:to-purple-500/20"
)} />
<Input
ref={ref}
className={cn(
// Size
"h-12 rounded-2xl",
// Background
"bg-white/80 dark:bg-slate-950/80 backdrop-blur-md",
// Border
"border border-slate-200/50 dark:border-slate-800/50",
// Hover
"hover:border-primary/20",
// Focus
"focus:border-primary/30 focus:ring-2 focus:ring-primary/20",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2",
// Transitions
"transition-all duration-300",
className
)}
{...props}
/>
</div>
);
}
);
NewInput.displayName = "NewInput";
Template 4: Badge Component¶
"use client";
import React from 'react';
import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib/utils';
export interface NewBadgeProps {
children: React.ReactNode;
variant?: 'default' | 'primary' | 'success' | 'error';
className?: string;
}
export function NewBadge({
children,
variant = 'default',
className,
}: NewBadgeProps) {
const variantStyles = {
default: "bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300",
primary: "bg-gradient-to-br from-primary/10 via-indigo-400/10 to-purple-400/10 text-primary",
success: "bg-success/10 text-success border-success/30",
error: "bg-error/10 text-error border-error/30",
};
return (
<Badge
className={cn(
"px-3 py-1 rounded-lg border",
variantStyles[variant],
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2",
className
)}
>
{children}
</Badge>
);
}
Checklist¶
Color System Compliance¶
- Uses standardized opacity values (
/10,/15,/20,/30,/50,/80) - Uses
400shades in light mode,500shades in dark mode - Uses 3-design-color system (Primary, Indigo+Purple, Blue)
- No hardcoded hex colors
- No non-standard colors (pink, cyan, violet, etc.)
Dark Mode Support¶
- All colors have dark mode variants
- Uses
dark:prefix for all dark mode classes - Dark mode colors use
500shades
Accessibility¶
- Focus state present (
ring-2 ring-primary) - Touch targets meet 44x44px minimum
- Contrast ratios meet WCAG requirements (4.5:1 for text, 3:1 for large text)
- Colorblind support (ring/border changes for state changes)
States¶
- Default state defined
- Hover state defined
- Focus state defined
- Active/selected state (if applicable)
- Disabled state (if applicable)
Code Quality¶
- TypeScript types defined
- Props documented with JSDoc
- Examples provided
- Follows existing component patterns
- Exported from index file
Common Patterns Reference¶
Background Gradient (Card)¶
"bg-gradient-to-br from-blue-400/30 via-indigo-400/20 via-purple-400/20 to-purple-400/15"
"dark:bg-gradient-to-br dark:from-blue-500/30 dark:via-indigo-500/20 dark:via-purple-500/20 dark:to-purple-500/15"
Background Gradient (Button)¶
"bg-gradient-to-br from-blue-400/30 via-indigo-400/30 via-purple-400/30 to-purple-400/20"
"dark:bg-gradient-to-br dark:from-blue-500/30 dark:via-indigo-500/30 dark:via-purple-500/30 dark:to-purple-500/20"
Border¶
Focus State (Required)¶
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
Hover Overlay¶
Hover Border¶
Hover Shadow¶
Active/Selected State¶
Disabled State¶
Real-World Examples¶
Example 1: Simple Action Button¶
"use client";
import React from 'react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
export interface ActionButtonProps {
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
className?: string;
}
export function ActionButton({
children,
onClick,
disabled = false,
className,
}: ActionButtonProps) {
return (
<Button
onClick={onClick}
disabled={disabled}
className={cn(
// Size - minimum 44x44px
"h-11 px-6 rounded-xl",
// Background gradient
"bg-gradient-to-br from-blue-400/30 via-indigo-400/30 to-purple-400/20",
"dark:bg-gradient-to-br dark:from-blue-500/30 dark:via-indigo-500/30 dark:to-purple-500/20",
// Border
"border border-slate-200/50 dark:border-slate-800/50",
// Text
"text-foreground dark:text-white font-semibold",
// Hover
"hover:bg-white/50 dark:hover:bg-black/50",
// Focus (required)
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2",
// Transitions
"transition-all duration-300",
// Disabled
disabled && "opacity-50 cursor-not-allowed",
className
)}
>
{children}
</Button>
);
}
Example 2: Status Badge¶
"use client";
import React from 'react';
import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib/utils';
export interface StatusBadgeProps {
status: 'success' | 'error' | 'warning' | 'info';
children: React.ReactNode;
className?: string;
}
export function StatusBadge({
status,
children,
className,
}: StatusBadgeProps) {
const statusStyles = {
success: "bg-success/10 text-success border-success/30",
error: "bg-error/10 text-error border-error/30",
warning: "bg-warning/10 text-warning border-warning/30",
info: "bg-info/10 text-info border-info/30",
};
return (
<Badge
className={cn(
"px-3 py-1 rounded-lg border",
statusStyles[status],
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2",
className
)}
>
{children}
</Badge>
);
}
Example 3: Interactive Card¶
"use client";
import React from 'react';
import { cn } from '@/lib/utils';
export interface InteractiveCardProps {
title: string;
description?: string;
onClick?: () => void;
className?: string;
}
export function InteractiveCard({
title,
description,
onClick,
className,
}: InteractiveCardProps) {
return (
<div
className={cn(
"group relative overflow-hidden",
// Background gradient
"bg-gradient-to-br from-blue-400/30 via-indigo-400/20 via-purple-400/20 to-purple-400/15",
"dark:bg-gradient-to-br dark:from-blue-500/30 dark:via-indigo-500/20 dark:via-purple-500/20 dark:to-purple-500/15",
// Border
"border border-slate-200/50 dark:border-slate-800/50",
// Hover effects
"hover:border-primary/30 hover:shadow-2xl hover:shadow-primary/10",
// Focus state
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2",
// Transitions
"transition-[transform,shadow,border-color] duration-300 ease-out",
// Clickable effects
"cursor-pointer hover:scale-[1.03] hover:-translate-y-1",
// Layout
"rounded-2xl p-6",
className
)}
onClick={onClick}
tabIndex={0}
role="button"
aria-label={title}
>
<h3 className="text-lg font-semibold text-foreground mb-2">{title}</h3>
{description && (
<p className="text-sm text-muted-foreground">{description}</p>
)}
</div>
);
}
Testing Checklist¶
Before considering a component complete:
Visual Testing¶
- Visual test in light mode
- Visual test in dark mode
- All states visible and working
- No visual regressions
Functional Testing¶
- All interactive states work
- Keyboard navigation works
- Click/tap interactions work
- No console errors
Accessibility Testing¶
- Focus state visible
- Keyboard navigation works
- Touch target size verified (44x44px minimum)
- Contrast ratios verified (4.5:1 for text, 3:1 for large text)
- Colorblind accessibility verified (ring/border changes)
- Screen reader tested (if applicable)
Code Quality¶
- TypeScript types correct
- No linter errors
- Props documented
- Examples provided
File Structure¶
When creating a new component, follow this structure:
frontend/lib/styles/components/
├── new-component.tsx # Component implementation
└── index.ts # Export (add to existing)
Component File Template¶
/**
* New Component
* Brief description of what this component does
* Used for [purpose]
*/
"use client";
import React from 'react';
import { cn } from '@/lib/utils';
// Import other dependencies as needed
/**
* Props for NewComponent
*/
export interface NewComponentProps {
/** Component content */
children: React.ReactNode;
/** Click handler */
onClick?: () => void;
/** Optional className for additional styling */
className?: string;
/** Optional disabled state */
disabled?: boolean;
}
/**
* New Component
*
* A reusable component that [description].
*
* @example
* ```tsx
* <NewComponent onClick={() => handleClick()}>
* Content
* </NewComponent>
* ```
*/
export function NewComponent({
children,
onClick,
className,
disabled = false,
}: NewComponentProps) {
return (
<div
className={cn(
// Add your classes here following the standards
className
)}
onClick={onClick}
// Add other props as needed
>
{children}
</div>
);
}
Integration Steps¶
After creating a new component:
- Add to Component Library
- Create file in
/lib/styles/components/ -
Follow naming convention (kebab-case)
-
Export from Index
- Add export to
/lib/styles/components/index.ts -
Export both component and types
-
Add to Style Demo
- Add component showcase to
/app/style-demo/page.tsx - Document all color properties
-
Add usage examples
-
Update Documentation
- Add to Component Colors Cheat Sheet if needed
- Update the Colors Styling Guide if new patterns introduced
Common Mistakes to Avoid¶
❌ Don't Do This¶
-
Hardcoded Colors: Don't use hex colors like
#3b82f6 -
Non-Standard Opacity: Don't use opacity values outside the 6-value scale
-
Wrong Color Shades: Don't use
500in light mode or400in dark mode -
Missing Dark Mode: Don't forget dark mode variants
-
Missing Focus State: Don't forget focus states for interactive elements
-
Too Small Touch Targets: Don't make interactive elements smaller than 44x44px
✅ Do This Instead¶
- Use CSS variables or Tailwind classes
- Use only the 6 standard opacity values
- Use
400for light mode,500for dark mode - Always include dark mode variants
- Always include focus states
- Ensure minimum 44x44px touch targets
Quick Reference¶
Color Selection Decision Tree¶
Is it a brand/primary element?
├─ Yes → Use `primary` CSS variable
└─ No → Is it a background/gradient?
├─ Yes → Use blue-indigo-purple gradient (400/500)
└─ No → Is it a border/text?
├─ Yes → Use slate colors
└─ No → Is it a state (success/error)?
├─ Yes → Use semantic colors
└─ No → Review component purpose
Opacity Selection Guide¶
Very subtle effect? → /10
Subtle background? → /15
Light background? → /20
Medium background? → /30 (most common)
Hover overlay? → /50
Strong overlay? → /80
Resources¶
- Colors Styling Guide: see Styling Guide 2.0 (guide-v2.md)
- Quick Reference: colors-reference.md
- Component Cheat Sheet: component-colors.md
- Migration Examples: migration-examples.md
- Existing Components:
/lib/styles/components/ - Style Demo:
/app/style-demo/page.tsx
Summary¶
Creating new components involves:
- ✅ Choose Component Type - Select appropriate pattern
- ✅ Choose Colors - Use 3-design-color system
- ✅ Apply Opacity - Use 6-value scale
- ✅ Add Dark Mode - Include
dark:variants - ✅ Add Focus State - Required for accessibility
- ✅ Add Hover State - Provide visual feedback
- ✅ Verify Touch Targets - Minimum 44x44px
- ✅ Test Accessibility - Contrast, keyboard, colorblind
- ✅ Test Thoroughly - All states and modes
- ✅ Document - Add to style-demo and guides
Result: Consistent, accessible, maintainable components that follow design system standards.
Next Steps¶
- ✅ Review existing components for patterns
- ✅ Use templates provided above
- ✅ Follow checklist
- ✅ Test thoroughly
- ✅ Document and integrate
For questions or clarifications, refer to the Colors Styling Guide.