Design Patterns
A collection of common UI patterns and best practices for building consistent and intuitive user interfaces with the SUDIGITAL design system.
Overview
Design patterns are reusable solutions to common design problems. They provide tested approaches for creating interfaces that users understand intuitively while maintaining consistency across the platform.
Navigation Patterns
Primary Navigation
The main navigation structure for applications.
Top Navigation Bar
typescript
import { Navbar, NavItem, Avatar, Button } from '@sudigital/design-system';
const TopNavigation = () => (
<Navbar>
<Navbar.Brand>
<Logo />
<Text variant="h6">SUDIGITAL</Text>
</Navbar.Brand>
<Navbar.Menu>
<NavItem href="/dashboard" active>Dashboard</NavItem>
<NavItem href="/projects">Projects</NavItem>
<NavItem href="/analytics">Analytics</NavItem>
<NavItem href="/settings">Settings</NavItem>
</Navbar.Menu>
<Navbar.Actions>
<Button variant="ghost" size="sm">
<NotificationIcon />
</Button>
<Avatar src="user.jpg" name="John Doe" />
</Navbar.Actions>
</Navbar>
);Sidebar Navigation
typescript
const SidebarNavigation = () => (
<Sidebar>
<Sidebar.Header>
<Logo />
<Text variant="h6">SUDIGITAL</Text>
</Sidebar.Header>
<Sidebar.Menu>
<Sidebar.Section title="Main">
<Sidebar.Item icon={<HomeIcon />} href="/dashboard" active>
Dashboard
</Sidebar.Item>
<Sidebar.Item icon={<ProjectIcon />} href="/projects">
Projects
</Sidebar.Item>
<Sidebar.Item icon={<AnalyticsIcon />} href="/analytics">
Analytics
</Sidebar.Item>
</Sidebar.Section>
<Sidebar.Section title="Account">
<Sidebar.Item icon={<SettingsIcon />} href="/settings">
Settings
</Sidebar.Item>
<Sidebar.Item icon={<UserIcon />} href="/profile">
Profile
</Sidebar.Item>
</Sidebar.Section>
</Sidebar.Menu>
<Sidebar.Footer>
<Button variant="outline" fullWidth>
<LogoutIcon />
Sign Out
</Button>
</Sidebar.Footer>
</Sidebar>
);Breadcrumb Navigation
typescript
const BreadcrumbPattern = () => {
const breadcrumbs = [
{ label: 'Dashboard', href: '/dashboard' },
{ label: 'Projects', href: '/projects' },
{ label: 'Website Redesign', href: '/projects/123' },
{ label: 'Design System' }
];
return (
<Stack direction="vertical" spacing="md">
<Breadcrumb items={breadcrumbs} />
<Text variant="h1">Design System</Text>
</Stack>
);
};Layout Patterns
Dashboard Layout
typescript
const DashboardLayout = ({ children }) => (
<div className="dashboard-layout">
<SidebarNavigation />
<div className="dashboard-content">
<TopNavigation />
<main className="dashboard-main">
{children}
</main>
</div>
</div>
);
// Usage
<DashboardLayout>
<Grid cols={{ xs: 1, md: 2, lg: 3 }} gap="lg">
<MetricCard title="Total Users" value="10,234" trend="+12%" />
<MetricCard title="Revenue" value="$45,678" trend="+8%" />
<MetricCard title="Growth Rate" value="15.3%" trend="+2.1%" />
</Grid>
</DashboardLayout>Content with Sidebar
typescript
const ContentSidebarLayout = ({ children, sidebar }) => (
<div className="content-layout">
<main className="content-main">
{children}
</main>
<aside className="content-sidebar">
{sidebar}
</aside>
</div>
);
// Usage
<ContentSidebarLayout
sidebar={
<Card>
<Card.Header>
<Text variant="h6">Quick Actions</Text>
</Card.Header>
<Card.Body>
<Stack direction="vertical" spacing="sm">
<Button variant="outline" fullWidth>New Project</Button>
<Button variant="outline" fullWidth>Import Data</Button>
<Button variant="outline" fullWidth>Export Report</Button>
</Stack>
</Card.Body>
</Card>
}
>
<Text variant="h1">Main Content</Text>
<p>Primary content goes here...</p>
</ContentSidebarLayout>Centered Layout
typescript
const CenteredLayout = ({ children, maxWidth = "lg" }) => (
<div className="centered-layout">
<Container maxWidth={maxWidth}>
{children}
</Container>
</div>
);
// Usage for forms, auth pages, etc.
<CenteredLayout maxWidth="sm">
<Card>
<Card.Header>
<Text variant="h3" align="center">Sign In</Text>
</Card.Header>
<Card.Body>
<LoginForm />
</Card.Body>
</Card>
</CenteredLayout>Form Patterns
Multi-Step Form
typescript
const MultiStepForm = () => {
const [currentStep, setCurrentStep] = useState(0);
const steps = ['Personal Info', 'Company Details', 'Preferences', 'Review'];
return (
<Card>
<Card.Header>
<ProgressSteps
steps={steps}
currentStep={currentStep}
/>
</Card.Header>
<Card.Body>
<Form onSubmit={handleSubmit}>
{currentStep === 0 && <PersonalInfoStep />}
{currentStep === 1 && <CompanyDetailsStep />}
{currentStep === 2 && <PreferencesStep />}
{currentStep === 3 && <ReviewStep />}
</Form>
</Card.Body>
<Card.Footer>
<Stack direction="horizontal" justify="space-between">
<Button
variant="outline"
disabled={currentStep === 0}
onClick={() => setCurrentStep(currentStep - 1)}
>
Previous
</Button>
<Button
variant="primary"
onClick={() => setCurrentStep(currentStep + 1)}
>
{currentStep === steps.length - 1 ? 'Submit' : 'Next'}
</Button>
</Stack>
</Card.Footer>
</Card>
);
};Inline Editing
typescript
const InlineEditableField = ({ value, onSave }) => {
const [isEditing, setIsEditing] = useState(false);
const [editValue, setEditValue] = useState(value);
const handleSave = () => {
onSave(editValue);
setIsEditing(false);
};
return (
<div className="inline-editable">
{isEditing ? (
<Stack direction="horizontal" spacing="sm">
<Input
value={editValue}
onChange={setEditValue}
autoFocus
/>
<Button size="sm" onClick={handleSave}>
<CheckIcon />
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => setIsEditing(false)}
>
<XIcon />
</Button>
</Stack>
) : (
<Stack direction="horizontal" spacing="sm" align="center">
<Text>{value}</Text>
<Button
size="sm"
variant="ghost"
onClick={() => setIsEditing(true)}
>
<EditIcon />
</Button>
</Stack>
)}
</div>
);
};Search and Filters
typescript
const SearchAndFilters = () => {
const [search, setSearch] = useState('');
const [filters, setFilters] = useState({});
return (
<Card>
<Card.Body>
<Stack direction="vertical" spacing="md">
{/* Search */}
<Input
leftIcon={<SearchIcon />}
placeholder="Search users..."
value={search}
onChange={setSearch}
/>
{/* Filters */}
<Stack direction="horizontal" spacing="md" wrap>
<Select
placeholder="Role"
options={[
{ value: 'admin', label: 'Admin' },
{ value: 'user', label: 'User' },
{ value: 'viewer', label: 'Viewer' }
]}
onChange={(value) => setFilters({...filters, role: value})}
/>
<Select
placeholder="Status"
options={[
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' }
]}
onChange={(value) => setFilters({...filters, status: value})}
/>
<Button
variant="outline"
onClick={() => {
setSearch('');
setFilters({});
}}
>
Clear Filters
</Button>
</Stack>
</Stack>
</Card.Body>
</Card>
);
};Data Display Patterns
Data Table with Actions
typescript
const UserDataTable = () => {
const columns = [
{
header: 'User',
accessor: 'user',
render: (user) => (
<Stack direction="horizontal" spacing="sm" align="center">
<Avatar src={user.avatar} name={user.name} size="sm" />
<div>
<Text variant="body-medium">{user.name}</Text>
<Text variant="caption" color="muted">{user.email}</Text>
</div>
</Stack>
)
},
{
header: 'Role',
accessor: 'role',
render: (role) => <Badge variant={role}>{role}</Badge>
},
{
header: 'Last Active',
accessor: 'lastActive',
render: (date) => <Text variant="caption">{formatDate(date)}</Text>
},
{
header: 'Actions',
render: (row) => (
<Dropdown
trigger={<Button variant="ghost" size="sm"><MoreIcon /></Button>}
>
<Dropdown.Item onClick={() => editUser(row.id)}>
<EditIcon /> Edit
</Dropdown.Item>
<Dropdown.Item onClick={() => viewUser(row.id)}>
<ViewIcon /> View Profile
</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item
color="error"
onClick={() => deleteUser(row.id)}
>
<DeleteIcon /> Delete
</Dropdown.Item>
</Dropdown>
)
}
];
return (
<Card>
<Card.Header>
<Stack direction="horizontal" justify="space-between" align="center">
<Text variant="h5">Users</Text>
<Button variant="primary">Add User</Button>
</Stack>
</Card.Header>
<Table
columns={columns}
data={users}
pagination
selection
sortable
/>
</Card>
);
};Metric Cards
typescript
const MetricCard = ({ title, value, trend, icon }) => (
<Card>
<Card.Body>
<Stack direction="horizontal" justify="space-between" align="start">
<div>
<Text variant="caption" color="muted">{title}</Text>
<Text variant="h3" style={{ marginTop: '4px' }}>{value}</Text>
{trend && (
<Stack direction="horizontal" spacing="xs" align="center">
<TrendIcon direction={trend.startsWith('+') ? 'up' : 'down'} />
<Text
variant="caption"
color={trend.startsWith('+') ? 'success' : 'error'}
>
{trend}
</Text>
</Stack>
)}
</div>
{icon && (
<div className="metric-icon">
{icon}
</div>
)}
</Stack>
</Card.Body>
</Card>
);
// Usage
<Grid cols={{ xs: 1, sm: 2, lg: 4 }} gap="lg">
<MetricCard
title="Total Revenue"
value="$124,532"
trend="+12.5%"
icon={<DollarIcon />}
/>
<MetricCard
title="Active Users"
value="8,234"
trend="+3.2%"
icon={<UsersIcon />}
/>
<MetricCard
title="Conversion Rate"
value="3.4%"
trend="-0.8%"
icon={<TargetIcon />}
/>
<MetricCard
title="Avg. Order Value"
value="$67.89"
trend="+5.1%"
icon={<ChartIcon />}
/>
</Grid>Status List
typescript
const StatusList = ({ items, title }) => (
<Card>
<Card.Header>
<Text variant="h6">{title}</Text>
</Card.Header>
<Card.Body style={{ padding: 0 }}>
<div className="status-list">
{items.map((item, index) => (
<div key={index} className="status-item">
<Stack direction="horizontal" spacing="md" align="center">
<StatusIndicator status={item.status} />
<div style={{ flex: 1 }}>
<Text variant="body-medium">{item.title}</Text>
<Text variant="caption" color="muted">{item.description}</Text>
</div>
<Text variant="caption" color="muted">
{formatTime(item.timestamp)}
</Text>
</Stack>
</div>
))}
</div>
</Card.Body>
</Card>
);Modal Patterns
Confirmation Dialog
typescript
const ConfirmationDialog = ({
isOpen,
onClose,
onConfirm,
title,
message,
confirmText = 'Confirm',
cancelText = 'Cancel',
variant = 'default'
}) => (
<Modal isOpen={isOpen} onClose={onClose} size="sm">
<Modal.Header>
<Stack direction="horizontal" spacing="md" align="center">
{variant === 'danger' && <AlertIcon color="error" />}
{variant === 'warning' && <WarningIcon color="warning" />}
<Text variant="h5">{title}</Text>
</Stack>
</Modal.Header>
<Modal.Body>
<Text>{message}</Text>
</Modal.Body>
<Modal.Footer>
<Stack direction="horizontal" spacing="sm" justify="end">
<Button variant="outline" onClick={onClose}>
{cancelText}
</Button>
<Button
variant={variant === 'danger' ? 'error' : 'primary'}
onClick={onConfirm}
>
{confirmText}
</Button>
</Stack>
</Modal.Footer>
</Modal>
);
// Usage
<ConfirmationDialog
isOpen={showDeleteDialog}
onClose={() => setShowDeleteDialog(false)}
onConfirm={handleDelete}
title="Delete User"
message="Are you sure you want to delete this user? This action cannot be undone."
confirmText="Delete"
variant="danger"
/>Form Modal
typescript
const UserFormModal = ({ isOpen, onClose, user, onSave }) => {
const [formData, setFormData] = useState(user || {});
const handleSubmit = async (data) => {
await onSave(data);
onClose();
};
return (
<Modal isOpen={isOpen} onClose={onClose} size="md">
<Modal.Header>
<Text variant="h5">
{user ? 'Edit User' : 'Add New User'}
</Text>
</Modal.Header>
<Form
initialValues={formData}
onSubmit={handleSubmit}
validationSchema={userSchema}
>
<Modal.Body>
<Stack direction="vertical" spacing="md">
<FormField name="name">
<Input label="Full Name" required />
</FormField>
<FormField name="email">
<Input label="Email" type="email" required />
</FormField>
<FormField name="role">
<Select
label="Role"
options={[
{ value: 'admin', label: 'Admin' },
{ value: 'user', label: 'User' },
{ value: 'viewer', label: 'Viewer' }
]}
required
/>
</FormField>
</Stack>
</Modal.Body>
<Modal.Footer>
<Stack direction="horizontal" spacing="sm" justify="end">
<Button variant="outline" onClick={onClose}>
Cancel
</Button>
<Button type="submit" variant="primary">
{user ? 'Update' : 'Create'}
</Button>
</Stack>
</Modal.Footer>
</Form>
</Modal>
);
};Loading and Error Patterns
Page Loading State
typescript
const PageWithLoading = ({ loading, error, children }) => {
if (loading) {
return (
<div className="page-loading">
<Stack direction="vertical" spacing="lg" align="center">
<Loading size="lg" />
<Text variant="body-large" color="muted">Loading...</Text>
</Stack>
</div>
);
}
if (error) {
return (
<div className="page-error">
<Card variant="outlined">
<Card.Body>
<Stack direction="vertical" spacing="md" align="center">
<AlertIcon size="xl" color="error" />
<Text variant="h5">Something went wrong</Text>
<Text color="muted" align="center">
{error.message || 'An unexpected error occurred.'}
</Text>
<Button onClick={() => window.location.reload()}>
Try Again
</Button>
</Stack>
</Card.Body>
</Card>
</div>
);
}
return children;
};Skeleton Loading
typescript
const UserListSkeleton = () => (
<Card>
<Card.Header>
<Skeleton width="120px" height="24px" />
</Card.Header>
<Card.Body>
<Stack direction="vertical" spacing="md">
{Array.from({ length: 5 }).map((_, index) => (
<Stack key={index} direction="horizontal" spacing="md" align="center">
<Skeleton.Circle size="40px" />
<div style={{ flex: 1 }}>
<Skeleton width="150px" height="16px" />
<Skeleton width="200px" height="14px" style={{ marginTop: '4px' }} />
</div>
<Skeleton width="80px" height="32px" />
</Stack>
))}
</Stack>
</Card.Body>
</Card>
);Empty States
typescript
const EmptyState = ({
icon,
title,
description,
action,
actionText = 'Get Started'
}) => (
<div className="empty-state">
<Stack direction="vertical" spacing="lg" align="center">
{icon && <div className="empty-state-icon">{icon}</div>}
<div style={{ textAlign: 'center' }}>
<Text variant="h5" style={{ marginBottom: '8px' }}>{title}</Text>
<Text color="muted">{description}</Text>
</div>
{action && (
<Button variant="primary" onClick={action}>
{actionText}
</Button>
)}
</Stack>
</div>
);
// Usage
<EmptyState
icon={<ProjectIcon size="xl" />}
title="No projects yet"
description="Create your first project to get started with SUDIGITAL."
action={() => setShowCreateProject(true)}
actionText="Create Project"
/>Responsive Patterns
Mobile-First Navigation
typescript
const ResponsiveNavigation = () => {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<>
{/* Desktop Navigation */}
<div className="hidden md:block">
<TopNavigation />
</div>
{/* Mobile Navigation */}
<div className="md:hidden">
<div className="mobile-nav-header">
<Logo />
<Button
variant="ghost"
onClick={() => setMobileMenuOpen(true)}
>
<MenuIcon />
</Button>
</div>
<Drawer
isOpen={mobileMenuOpen}
onClose={() => setMobileMenuOpen(false)}
placement="left"
>
<MobileMenu onItemClick={() => setMobileMenuOpen(false)} />
</Drawer>
</div>
</>
);
};Responsive Grid
typescript
const ResponsiveGrid = ({ children }) => (
<Grid
cols={{
xs: 1, // 1 column on mobile
sm: 2, // 2 columns on small tablets
md: 3, // 3 columns on tablets
lg: 4, // 4 columns on desktop
xl: 5 // 5 columns on large desktop
}}
gap={{
xs: 'sm', // Small gap on mobile
md: 'md', // Medium gap on tablet
lg: 'lg' // Large gap on desktop
}}
>
{children}
</Grid>
);Best Practices
Consistency Guidelines
- Use Standard Patterns: Stick to established patterns for common interactions
- Maintain Visual Hierarchy: Use typography and spacing consistently
- Follow Platform Conventions: Respect platform-specific interaction patterns
- Test Across Devices: Ensure patterns work on all target devices
Performance Considerations
- Lazy Load: Use lazy loading for non-critical content
- Optimize Images: Use appropriate image formats and sizes
- Minimize Reflows: Avoid layout thrashing with proper CSS
- Progressive Enhancement: Build for the lowest common denominator first
Accessibility
- Keyboard Navigation: Ensure all patterns are keyboard accessible
- Screen Reader Support: Use proper ARIA labels and semantic HTML
- Focus Management: Provide clear focus indicators and logical tab order
- Color Independence: Don't rely solely on color to convey information
Testing Patterns
typescript
// Test responsive behavior
describe('ResponsiveGrid', () => {
test('adapts to different screen sizes', () => {
render(<ResponsiveGrid>{children}</ResponsiveGrid>);
// Test mobile layout
global.innerWidth = 480;
global.dispatchEvent(new Event('resize'));
expect(screen.getByTestId('grid')).toHaveClass('grid-cols-1');
// Test desktop layout
global.innerWidth = 1024;
global.dispatchEvent(new Event('resize'));
expect(screen.getByTestId('grid')).toHaveClass('lg:grid-cols-4');
});
});Related Resources
- Design System - Foundation and tokens
- Component Library - Individual components
- Accessibility Guidelines - WCAG 2.1 reference
- Responsive Design - Best practices