UI Components Examples
Common UI component patterns and examples for Fireact applications.
Overview
This page provides reusable UI component examples built with React, TypeScript, and TailwindCSS for Fireact applications.
Form Components
Input with Validation
// src/components/forms/ValidatedInput.tsx
import React from 'react';
interface ValidatedInputProps {
label: string;
name: string;
type?: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
error?: string;
required?: boolean;
placeholder?: string;
}
export const ValidatedInput: React.FC<ValidatedInputProps> = ({
label,
name,
type = 'text',
value,
onChange,
error,
required = false,
placeholder,
}) => {
return (
<div className="space-y-1">
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
{label}
{required && <span className="text-red-500 ml-1">*</span>}
</label>
<input
type={type}
id={name}
name={name}
value={value}
onChange={onChange}
placeholder={placeholder}
className={`
w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2
${error
? 'border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:ring-blue-500'
}
`}
/>
{error && <p className="text-sm text-red-600">{error}</p>}
</div>
);
};
Select Dropdown
// src/components/forms/Select.tsx
import React from 'react';
interface Option {
value: string;
label: string;
}
interface SelectProps {
label: string;
name: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
options: Option[];
error?: string;
required?: boolean;
}
export const Select: React.FC<SelectProps> = ({
label,
name,
value,
onChange,
options,
error,
required = false,
}) => {
return (
<div className="space-y-1">
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
{label}
{required && <span className="text-red-500 ml-1">*</span>}
</label>
<select
id={name}
name={name}
value={value}
onChange={onChange}
className={`
w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2
${error
? 'border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:ring-blue-500'
}
`}
>
<option value="">Select an option</option>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
{error && <p className="text-sm text-red-600">{error}</p>}
</div>
);
};
Toggle Switch
// src/components/forms/Toggle.tsx
import React from 'react';
interface ToggleProps {
label: string;
enabled: boolean;
onChange: (enabled: boolean) => void;
description?: string;
}
export const Toggle: React.FC<ToggleProps> = ({
label,
enabled,
onChange,
description,
}) => {
return (
<div className="flex items-center justify-between">
<div className="flex-1">
<p className="text-sm font-medium text-gray-700">{label}</p>
{description && <p className="text-sm text-gray-500">{description}</p>}
</div>
<button
type="button"
onClick={() => onChange(!enabled)}
className={`
relative inline-flex h-6 w-11 items-center rounded-full transition-colors
${enabled ? 'bg-blue-600' : 'bg-gray-200'}
`}
>
<span
className={`
inline-block h-4 w-4 transform rounded-full bg-white transition-transform
${enabled ? 'translate-x-6' : 'translate-x-1'}
`}
/>
</button>
</div>
);
};
Card Components
Basic Card
// src/components/cards/Card.tsx
import React from 'react';
interface CardProps {
title?: string;
children: React.ReactNode;
footer?: React.ReactNode;
className?: string;
}
export const Card: React.FC<CardProps> = ({
title,
children,
footer,
className = '',
}) => {
return (
<div className={`bg-white rounded-lg shadow-md ${className}`}>
{title && (
<div className="px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-900">{title}</h3>
</div>
)}
<div className="px-6 py-4">{children}</div>
{footer && (
<div className="px-6 py-4 border-t border-gray-200 bg-gray-50 rounded-b-lg">
{footer}
</div>
)}
</div>
);
};
Stat Card
// src/components/cards/StatCard.tsx
import React from 'react';
interface StatCardProps {
title: string;
value: string | number;
change?: {
value: number;
type: 'increase' | 'decrease';
};
icon?: React.ReactNode;
}
export const StatCard: React.FC<StatCardProps> = ({
title,
value,
change,
icon,
}) => {
return (
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex items-center justify-between">
<div className="flex-1">
<p className="text-sm font-medium text-gray-600">{title}</p>
<p className="mt-2 text-3xl font-bold text-gray-900">{value}</p>
{change && (
<div className="mt-2 flex items-center">
<span
className={`text-sm font-medium ${
change.type === 'increase' ? 'text-green-600' : 'text-red-600'
}`}
>
{change.type === 'increase' ? '↑' : '↓'} {Math.abs(change.value)}%
</span>
<span className="ml-2 text-sm text-gray-600">vs last month</span>
</div>
)}
</div>
{icon && (
<div className="p-3 bg-blue-50 rounded-lg text-blue-600">{icon}</div>
)}
</div>
</div>
);
};
Modal Components
Basic Modal
// src/components/modals/Modal.tsx
import React, { useEffect } from 'react';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
footer?: React.ReactNode;
}
export const Modal: React.FC<ModalProps> = ({
isOpen,
onClose,
title,
children,
footer,
}) => {
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
if (isOpen) {
document.addEventListener('keydown', handleEscape);
document.body.style.overflow = 'hidden';
}
return () => {
document.removeEventListener('keydown', handleEscape);
document.body.style.overflow = 'unset';
};
}, [isOpen, onClose]);
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex min-h-screen items-center justify-center p-4">
{/* Backdrop */}
<div
className="fixed inset-0 bg-black bg-opacity-50 transition-opacity"
onClick={onClose}
/>
{/* Modal */}
<div className="relative bg-white rounded-lg shadow-xl max-w-lg w-full">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b">
<h3 className="text-lg font-semibold text-gray-900">{title}</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
>
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Body */}
<div className="px-6 py-4">{children}</div>
{/* Footer */}
{footer && (
<div className="px-6 py-4 border-t bg-gray-50 rounded-b-lg">
{footer}
</div>
)}
</div>
</div>
</div>
);
};
Confirmation Modal
// src/components/modals/ConfirmModal.tsx
import React from 'react';
import { Modal } from './Modal';
interface ConfirmModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
title: string;
message: string;
confirmText?: string;
cancelText?: string;
variant?: 'danger' | 'warning' | 'info';
}
export const ConfirmModal: React.FC<ConfirmModalProps> = ({
isOpen,
onClose,
onConfirm,
title,
message,
confirmText = 'Confirm',
cancelText = 'Cancel',
variant = 'info',
}) => {
const variantColors = {
danger: 'bg-red-600 hover:bg-red-700',
warning: 'bg-yellow-600 hover:bg-yellow-700',
info: 'bg-blue-600 hover:bg-blue-700',
};
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title={title}
footer={
<div className="flex justify-end space-x-3">
<button
onClick={onClose}
className="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50"
>
{cancelText}
</button>
<button
onClick={() => {
onConfirm();
onClose();
}}
className={`px-4 py-2 text-white rounded-lg ${variantColors[variant]}`}
>
{confirmText}
</button>
</div>
}
>
<p className="text-gray-600">{message}</p>
</Modal>
);
};
Loading & Empty States
Loading Spinner
// src/components/feedback/LoadingSpinner.tsx
import React from 'react';
interface LoadingSpinnerProps {
size?: 'sm' | 'md' | 'lg';
className?: string;
}
export const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
size = 'md',
className = '',
}) => {
const sizeClasses = {
sm: 'w-4 h-4',
md: 'w-8 h-8',
lg: 'w-12 h-12',
};
return (
<div className={`flex justify-center items-center ${className}`}>
<div
className={`${sizeClasses[size]} border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin`}
/>
</div>
);
};
Empty State
// src/components/feedback/EmptyState.tsx
import React from 'react';
interface EmptyStateProps {
title: string;
description?: string;
icon?: React.ReactNode;
action?: {
label: string;
onClick: () => void;
};
}
export const EmptyState: React.FC<EmptyStateProps> = ({
title,
description,
icon,
action,
}) => {
return (
<div className="flex flex-col items-center justify-center py-12 px-4">
{icon && <div className="mb-4 text-gray-400">{icon}</div>}
<h3 className="text-lg font-medium text-gray-900">{title}</h3>
{description && (
<p className="mt-2 text-sm text-gray-600 text-center max-w-md">
{description}
</p>
)}
{action && (
<button
onClick={action.onClick}
className="mt-6 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
{action.label}
</button>
)}
</div>
);
};
Alert Components
Alert Banner
// src/components/feedback/Alert.tsx
import React from 'react';
interface AlertProps {
type: 'success' | 'error' | 'warning' | 'info';
title?: string;
message: string;
onClose?: () => void;
}
export const Alert: React.FC<AlertProps> = ({ type, title, message, onClose }) => {
const styles = {
success: {
bg: 'bg-green-50',
border: 'border-green-200',
text: 'text-green-800',
icon: '✓',
},
error: {
bg: 'bg-red-50',
border: 'border-red-200',
text: 'text-red-800',
icon: '✕',
},
warning: {
bg: 'bg-yellow-50',
border: 'border-yellow-200',
text: 'text-yellow-800',
icon: '⚠',
},
info: {
bg: 'bg-blue-50',
border: 'border-blue-200',
text: 'text-blue-800',
icon: 'ℹ',
},
};
const style = styles[type];
return (
<div className={`${style.bg} ${style.border} border rounded-lg p-4`}>
<div className="flex items-start">
<span className={`${style.text} text-xl mr-3`}>{style.icon}</span>
<div className="flex-1">
{title && (
<h4 className={`${style.text} font-medium mb-1`}>{title}</h4>
)}
<p className={`${style.text} text-sm`}>{message}</p>
</div>
{onClose && (
<button onClick={onClose} className={`${style.text} ml-3`}>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" />
</svg>
</button>
)}
</div>
</div>
);
};
Table Components
Data Table
// src/components/table/DataTable.tsx
import React from 'react';
interface Column<T> {
key: string;
header: string;
render?: (item: T) => React.ReactNode;
}
interface DataTableProps<T> {
columns: Column<T>[];
data: T[];
keyExtractor: (item: T) => string;
}
export function DataTable<T>({ columns, data, keyExtractor }: DataTableProps<T>) {
return (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
{columns.map((column) => (
<th
key={column.key}
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{column.header}
</th>
))}
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{data.map((item) => (
<tr key={keyExtractor(item)}>
{columns.map((column) => (
<td
key={column.key}
className="px-6 py-4 whitespace-nowrap text-sm text-gray-900"
>
{column.render
? column.render(item)
: String((item as any)[column.key])}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
// Usage example
interface User {
id: string;
name: string;
email: string;
role: string;
}
const UsersTable = () => {
const users: User[] = [
{ id: '1', name: 'John Doe', email: 'john@example.com', role: 'admin' },
];
const columns: Column<User>[] = [
{ key: 'name', header: 'Name' },
{ key: 'email', header: 'Email' },
{
key: 'role',
header: 'Role',
render: (user) => (
<span className="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs">
{user.role}
</span>
),
},
];
return <DataTable columns={columns} data={users} keyExtractor={(u) => u.id} />;
};
Best Practices
- Use TypeScript interfaces for all props
- Make components reusable with flexible props
- Apply consistent styling with TailwindCSS
- Handle loading and error states
- Ensure accessibility (ARIA labels, keyboard navigation)
- Keep components focused (single responsibility)
- Use composition over complex props
- Test components thoroughly
See Also
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.