Component Design Patterns
Explore best practices for designing components in UI, focusing on reusable and scalable design patterns in CenSuite. This guide covers component states, state management, consistency across screens, and handling error states for better user interactions.
Introduction
Designing effective and scalable components is essential for creating user interfaces that are consistent, maintainable, and easy to use. This guide explores how to design components like buttons, inputs, modals, and cards with flexibility and modularity to ensure they can be reused across various contexts.
Buttons
Buttons are fundamental UI elements used to trigger actions. In CenSuite, buttons are designed to be customizable, offering various styles, states, and sizes to accommodate different use cases.
import { Button } from "@/components/ui/button"
export default function ButtonExample() {
return (
<div className="flex space-x-4">
<Button variant="default">Default</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
</div>
)
}
Inputs
Inputs are interfaces for user data entry and include text fields, checkboxes, radio buttons, and dropdowns. They should adhere to accessibility guidelines and provide clear visual feedback for different states.
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export default function InputExample() {
return (
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="email">Email</Label>
<Input type="email" id="email" placeholder="Email" />
</div>
)
}
Modals
Modals provide content or prompt user actions without navigating away from the current screen. They should be accessible and handle responsive layouts effectively.
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
export default function ModalExample() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Open Modal</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your account
and remove your data from our servers.
</DialogDescription>
</DialogHeader>
<div className="flex justify-end space-x-2">
<Button variant="outline">Cancel</Button>
<Button variant="destructive">Delete Account</Button>
</div>
</DialogContent>
</Dialog>
)
}
Cards
Cards are versatile UI containers used for displaying related information like product listings or user profiles. They should maintain consistent padding, spacing, and shadowing.
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Button } from "@/components/ui/button"
export default function CardExample() {
return (
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card Description</CardDescription>
</CardHeader>
<CardContent>
<p>Card Content</p>
</CardContent>
<CardFooter>
<Button>Action</Button>
</CardFooter>
</Card>
)
}
Component States: Hover, Focus, Active, Disabled, and Feedback Patterns
It is crucial to design components with various states to enhance user interaction and accessibility. This section discusses different states such as hover, focus, active, and disabled.
Hover
Hover states provide a visual indication of interactivity, typically through changes in background color, shadow, or border.
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
export default function HoverExample() {
return (
<div className="space-y-4">
<Button>Default Hover</Button>
<Button className={cn("transition-all duration-300", "hover:bg-blue-600 hover:text-white")}>
Custom Hover
</Button>
</div>
)
}
Focus
Focus states are essential for accessibility, especially for inputs and interactive elements. They help users navigate with keyboard and indicate which element has the focus.
import { Input } from "@/components/ui/input"
import { cn } from "@/lib/utils"
export default function FocusExample() {
return (
<div className="space-y-4">
<Input placeholder="Default Focus" />
<Input className={cn("focus:ring-2 focus:ring-blue-500 focus:border-blue-500", "focus:outline-none")} placeholder="Custom Focus" />
</div>
)
}
Active
Active states indicate an element has been activated or clicked, often shown by a change in background color.
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
export default function ActiveExample() {
return (
<div className="space-y-4">
<Button>Default Active</Button>
<Button className={cn("active:bg-green-600 active:text-white", "transition-colors duration-200")}>
Custom Active
</Button>
</div>
)
}
Disabled
Disabled states show that a component is not interactive, typically using muted colors or reduced opacity.
Reusable Components: Best Practices for Modular, Scalable Components
Reusable components are the foundation of a scalable UI system. This section outlines best practices for designing components that are easy to maintain and adaptable across various parts of your application.
Best Practices for Reusability
- Modularity: Design components to be self-contained with clear boundaries.
- Customizability: Allow components to accept props for configuration, enabling consistent yet adaptable use across the application.
- Separation of Concerns: Maintain a separation between logic and styles to ensure clean, maintainable code.
Here's an example of a reusable component:
import React from "react"
import { cn } from "@/lib/utils"
import { Button, ButtonProps } from "@/components/ui/button"
interface IconButtonProps extends ButtonProps {
icon: React.ReactNode
}
export const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
({ className, icon, children, ...props }, ref) => {
return (
<Button ref={ref} className={cn("flex items-center space-x-2", className)} {...props}>
{icon}
<span>{children}</span>
</Button>
)
}
)
IconButton.displayName = "IconButton"
Usage:
import { IconButton } from "@/components/icon-button"
import { Plus } from "lucide-react"
export default function IconButtonExample() {
return (
<IconButton icon={<Plus className="h-4 w-4" />}>
Add Item
</IconButton>
)
}
By following these design patterns, you can create components that are not only functional but also intuitive and accessible, enhancing the user experience across your application.