Skip to main content
Glama
component-library.md•18 kB
# Component Library Patterns ## Overview Reusable component patterns extracted from professional Next.js templates. These are common UI building blocks with consistent styling and behavior patterns. ## Pattern A: Button Components ### Characteristics - Multiple variants (solid, outline, ghost, text) - Color schemes (primary, secondary, danger, success) - Sizes (sm, md, lg, xl) - Polymorphic (renders as button or link) - States (default, hover, active, disabled, loading) ### Base Implementation ```tsx import { type ButtonHTMLAttributes, type AnchorHTMLAttributes } from 'react' import Link from 'next/link' import { cva, type VariantProps } from 'class-variance-authority' const buttonVariants = cva( // Base styles 'inline-flex items-center justify-center rounded-lg font-semibold transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { solid: '', outline: 'border-2', ghost: '', text: 'underline-offset-4 hover:underline', }, color: { primary: '', secondary: '', danger: '', white: '', }, size: { sm: 'h-9 px-3 text-sm', md: 'h-10 px-4 text-sm', lg: 'h-11 px-6 text-base', xl: 'h-12 px-8 text-base', }, }, compoundVariants: [ // Solid variants { variant: 'solid', color: 'primary', className: 'bg-indigo-600 text-white hover:bg-indigo-500 focus-visible:outline-indigo-600', }, { variant: 'solid', color: 'secondary', className: 'bg-gray-900 text-white hover:bg-gray-700 focus-visible:outline-gray-900', }, { variant: 'solid', color: 'danger', className: 'bg-red-600 text-white hover:bg-red-500 focus-visible:outline-red-600', }, { variant: 'solid', color: 'white', className: 'bg-white text-gray-900 hover:bg-gray-50 focus-visible:outline-white', }, // Outline variants { variant: 'outline', color: 'primary', className: 'border-indigo-600 text-indigo-600 hover:bg-indigo-50 focus-visible:outline-indigo-600', }, { variant: 'outline', color: 'secondary', className: 'border-gray-300 text-gray-700 hover:bg-gray-50 focus-visible:outline-gray-900', }, // Ghost variants { variant: 'ghost', color: 'primary', className: 'text-indigo-600 hover:bg-indigo-50 focus-visible:outline-indigo-600', }, { variant: 'ghost', color: 'secondary', className: 'text-gray-700 hover:bg-gray-100 focus-visible:outline-gray-900', }, ], defaultVariants: { variant: 'solid', color: 'primary', size: 'md', }, } ) type ButtonProps = VariantProps<typeof buttonVariants> & ( | ({ href?: never } & ButtonHTMLAttributes<HTMLButtonElement>) | ({ href: string } & AnchorHTMLAttributes<HTMLAnchorElement>) ) export function Button({ variant, color, size, className, href, ...props }: ButtonProps) { const classes = buttonVariants({ variant, color, size, className }) if (href) { return <Link href={href} className={classes} {...props as any} /> } return <button className={classes} {...props as any} /> } ``` ### Loading State ```tsx export function Button({ loading, children, disabled, ...props }: ButtonProps & { loading?: boolean }) { return ( <button disabled={disabled || loading} {...props}> {loading && ( <svg className="mr-2 h-4 w-4 animate-spin" viewBox="0 0 24 24"> <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" /> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /> </svg> )} {children} </button> ) } ``` ### Icon Button ```tsx export function IconButton({ icon: Icon, children, ...props }: ButtonProps & { icon?: React.ComponentType<{ className?: string }> }) { return ( <Button {...props}> {Icon && <Icon className="mr-2 h-4 w-4" />} {children} </Button> ) } ``` ## Pattern B: Card Components ### Base Card ```tsx export function Card({ children, className, ...props }: React.HTMLAttributes<HTMLDivElement>) { return ( <div className={`rounded-2xl bg-white p-6 shadow-lg ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-gray-800 ${className}`} {...props} > {children} </div> ) } ``` ### Card with Hover Effect ```tsx export function InteractiveCard({ children, href, ...props }: React.HTMLAttributes<HTMLDivElement> & { href?: string }) { const content = ( <div className="group rounded-2xl bg-white p-6 shadow-md ring-1 ring-gray-200 transition-all hover:shadow-xl hover:ring-gray-300 dark:bg-gray-900 dark:ring-gray-800 dark:hover:ring-gray-700"> {children} </div> ) if (href) { return <Link href={href}>{content}</Link> } return content } ``` ### Feature Card Pattern ```tsx export function FeatureCard({ icon: Icon, title, description }: { icon: React.ComponentType<{ className?: string }> title: string description: string }) { return ( <div className="rounded-2xl border border-gray-200 p-8 dark:border-gray-800"> <div className="mb-4 inline-flex rounded-lg bg-indigo-100 p-3 dark:bg-indigo-900/20"> <Icon className="h-6 w-6 text-indigo-600 dark:text-indigo-400" /> </div> <h3 className="mb-2 text-lg font-semibold text-gray-900 dark:text-white"> {title} </h3> <p className="text-gray-600 dark:text-gray-400"> {description} </p> </div> ) } ``` ### Gradient Card ```tsx export function GradientCard({ children }: { children: React.ReactNode }) { return ( <div className="group relative"> {/* Gradient border */} <div className="absolute -inset-0.5 rounded-2xl bg-gradient-to-r from-pink-600 to-purple-600 opacity-75 blur transition duration-1000 group-hover:opacity-100" /> {/* Card content */} <div className="relative rounded-2xl bg-white p-6 dark:bg-gray-900"> {children} </div> </div> ) } ``` ## Pattern C: Badge & Pill Components ### Badge Variants ```tsx const badgeVariants = cva( 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold', { variants: { variant: { solid: '', outline: 'border', soft: '', }, color: { gray: '', blue: '', green: '', yellow: '', red: '', }, }, compoundVariants: [ // Solid variants { variant: 'solid', color: 'gray', className: 'bg-gray-900 text-white dark:bg-gray-100 dark:text-gray-900', }, { variant: 'solid', color: 'blue', className: 'bg-blue-600 text-white', }, { variant: 'solid', color: 'green', className: 'bg-green-600 text-white', }, { variant: 'solid', color: 'yellow', className: 'bg-yellow-500 text-white', }, { variant: 'solid', color: 'red', className: 'bg-red-600 text-white', }, // Soft variants { variant: 'soft', color: 'gray', className: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300', }, { variant: 'soft', color: 'blue', className: 'bg-blue-100 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400', }, { variant: 'soft', color: 'green', className: 'bg-green-100 text-green-700 dark:bg-green-900/20 dark:text-green-400', }, { variant: 'soft', color: 'yellow', className: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/20 dark:text-yellow-400', }, { variant: 'soft', color: 'red', className: 'bg-red-100 text-red-700 dark:bg-red-900/20 dark:text-red-400', }, ], defaultVariants: { variant: 'soft', color: 'gray', }, } ) export function Badge({ variant, color, children, className }: VariantProps<typeof badgeVariants> & { children: React.ReactNode; className?: string }) { return ( <span className={badgeVariants({ variant, color, className })}> {children} </span> ) } ``` ### Badge with Dot Indicator ```tsx export function StatusBadge({ status, children }: { status: 'success' | 'warning' | 'error' | 'info'; children: React.ReactNode }) { const colors = { success: 'bg-green-500', warning: 'bg-yellow-500', error: 'bg-red-500', info: 'bg-blue-500', } return ( <span className="inline-flex items-center gap-x-1.5 rounded-full bg-gray-100 px-2.5 py-1 text-xs font-medium text-gray-700 dark:bg-gray-800 dark:text-gray-300"> <span className={`h-1.5 w-1.5 rounded-full ${colors[status]}`} /> {children} </span> ) } ``` ## Pattern D: Input & Form Field Components ### Text Input ```tsx export function Input({ label, error, ...props }: React.InputHTMLAttributes<HTMLInputElement> & { label?: string; error?: string }) { return ( <div> {label && ( <label htmlFor={props.id} className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"> {label} </label> )} <input className={`block w-full rounded-lg border ${ error ? 'border-red-300 text-red-900 placeholder-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900' } px-3 py-2 shadow-sm focus:outline-none focus:ring-1 sm:text-sm`} {...props} /> {error && ( <p className="mt-2 text-sm text-red-600 dark:text-red-400">{error}</p> )} </div> ) } ``` ### Textarea ```tsx export function Textarea({ label, error, ...props }: React.TextareaHTMLAttributes<HTMLTextAreaElement> & { label?: string; error?: string }) { return ( <div> {label && ( <label htmlFor={props.id} className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"> {label} </label> )} <textarea className={`block w-full rounded-lg border ${ error ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900' } px-3 py-2 shadow-sm focus:outline-none focus:ring-1 sm:text-sm`} {...props} /> {error && ( <p className="mt-2 text-sm text-red-600 dark:text-red-400">{error}</p> )} </div> ) } ``` ## Pattern E: Container Components ### Standard Container ```tsx export function Container({ children, className }: { children: React.ReactNode; className?: string }) { return ( <div className={`mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 ${className || ''}`}> {children} </div> ) } ``` ### Narrow Container (for content) ```tsx export function NarrowContainer({ children }: { children: React.ReactNode }) { return ( <div className="mx-auto max-w-2xl px-4 sm:px-6 lg:px-8"> {children} </div> ) } ``` ### Section Container ```tsx export function Section({ children, className }: { children: React.ReactNode; className?: string }) { return ( <section className={`py-20 sm:py-32 ${className || ''}`}> <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> {children} </div> </section> ) } ``` ## Pattern F: Avatar Components ### Simple Avatar ```tsx import Image from 'next/image' export function Avatar({ src, alt, size = 'md' }: { src: string; alt: string; size?: 'sm' | 'md' | 'lg' | 'xl' }) { const sizes = { sm: 'h-8 w-8', md: 'h-10 w-10', lg: 'h-12 w-12', xl: 'h-16 w-16', } return ( <div className={`relative ${sizes[size]} overflow-hidden rounded-full bg-gray-100`}> <Image src={src} alt={alt} fill className="object-cover" /> </div> ) } ``` ### Avatar with Ring ```tsx export function AvatarWithRing({ src, alt }: { src: string; alt: string }) { return ( <div className="relative h-12 w-12 overflow-hidden rounded-full ring-2 ring-white shadow-lg dark:ring-gray-900"> <Image src={src} alt={alt} fill className="object-cover" /> </div> ) } ``` ### Initials Avatar ```tsx export function InitialsAvatar({ name, size = 'md' }: { name: string; size?: 'sm' | 'md' | 'lg' }) { const sizes = { sm: 'h-8 w-8 text-xs', md: 'h-10 w-10 text-sm', lg: 'h-12 w-12 text-base', } const initials = name .split(' ') .map(n => n[0]) .join('') .toUpperCase() .slice(0, 2) return ( <div className={`inline-flex ${sizes[size]} items-center justify-center rounded-full bg-indigo-600 font-semibold text-white`}> {initials} </div> ) } ``` ## Pattern G: Divider Components ### Simple Divider ```tsx export function Divider({ className }: { className?: string }) { return <hr className={`border-gray-200 dark:border-gray-800 ${className || ''}`} /> } ``` ### Divider with Text ```tsx export function DividerWithText({ children }: { children: React.ReactNode }) { return ( <div className="relative"> <div className="absolute inset-0 flex items-center" aria-hidden="true"> <div className="w-full border-t border-gray-200 dark:border-gray-800" /> </div> <div className="relative flex justify-center"> <span className="bg-white px-2 text-sm text-gray-500 dark:bg-gray-950 dark:text-gray-400"> {children} </span> </div> </div> ) } ``` ## Pattern H: Loading & Skeleton Components ### Spinner ```tsx export function Spinner({ size = 'md' }: { size?: 'sm' | 'md' | 'lg' }) { const sizes = { sm: 'h-4 w-4', md: 'h-8 w-8', lg: 'h-12 w-12', } return ( <svg className={`animate-spin text-indigo-600 ${sizes[size]}`} viewBox="0 0 24 24"> <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" /> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /> </svg> ) } ``` ### Skeleton Loaders ```tsx export function Skeleton({ className }: { className?: string }) { return ( <div className={`animate-pulse rounded-lg bg-gray-200 dark:bg-gray-800 ${className || ''}`} /> ) } export function SkeletonCard() { return ( <div className="rounded-2xl border border-gray-200 p-6 dark:border-gray-800"> <Skeleton className="mb-4 h-12 w-12 rounded-lg" /> <Skeleton className="mb-2 h-4 w-3/4" /> <Skeleton className="h-3 w-full" /> <Skeleton className="mt-2 h-3 w-5/6" /> </div> ) } ``` ## Pattern I: Alert/Toast Components ### Alert ```tsx const alertVariants = cva( 'rounded-lg p-4', { variants: { variant: { info: 'bg-blue-50 text-blue-900 dark:bg-blue-900/20 dark:text-blue-200', success: 'bg-green-50 text-green-900 dark:bg-green-900/20 dark:text-green-200', warning: 'bg-yellow-50 text-yellow-900 dark:bg-yellow-900/20 dark:text-yellow-200', error: 'bg-red-50 text-red-900 dark:bg-red-900/20 dark:text-red-200', }, }, defaultVariants: { variant: 'info', }, } ) export function Alert({ variant, title, children }: VariantProps<typeof alertVariants> & { title?: string; children: React.ReactNode }) { return ( <div className={alertVariants({ variant })}> {title && <h3 className="mb-1 font-semibold">{title}</h3>} <div className="text-sm">{children}</div> </div> ) } ``` ## Pattern J: Tooltip Component ### Simple Tooltip ```tsx 'use client' import { useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' export function Tooltip({ children, content }: { children: React.ReactNode; content: string }) { const [isVisible, setIsVisible] = useState(false) return ( <div className="relative inline-block"> <div onMouseEnter={() => setIsVisible(true)} onMouseLeave={() => setIsVisible(false)} > {children} </div> <AnimatePresence> {isVisible && ( <motion.div initial={{ opacity: 0, y: 5 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 5 }} className="absolute bottom-full left-1/2 mb-2 -translate-x-1/2 whitespace-nowrap rounded-lg bg-gray-900 px-3 py-1.5 text-xs text-white shadow-lg dark:bg-gray-700" > {content} <div className="absolute left-1/2 top-full -translate-x-1/2"> <div className="border-4 border-transparent border-t-gray-900 dark:border-t-gray-700" /> </div> </motion.div> )} </AnimatePresence> </div> ) } ``` ## Common Patterns Summary ### Consistency Keys - **Spacing**: Use consistent padding/margin scale (4, 6, 8, 12, 16, 20, 24) - **Border Radius**: rounded-lg (8px), rounded-xl (12px), rounded-2xl (16px) - **Shadows**: shadow-sm, shadow, shadow-lg, shadow-xl - **Transitions**: transition-colors, transition-all duration-200/300 - **Focus States**: Always include focus-visible:outline ### Dark Mode Pattern ```tsx className="bg-white text-gray-900 dark:bg-gray-900 dark:text-white" ``` ### Hover States Pattern ```tsx className="hover:bg-gray-100 transition-colors duration-200 dark:hover:bg-gray-800" ``` ## Dependencies - `class-variance-authority` - Variant management - `clsx` - Conditional classnames - `next/image` - Optimized images - `framer-motion` - Animations (optional)

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/CaullenOmdahl/Nextjs-React-Tailwind-Assistant'

If you have feedback or need assistance with the MCP directory API, please join our Discord server