Skip to content

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.

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>
);
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>
);
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>
);

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

  1. Use Standard Patterns: Stick to established patterns for common interactions
  2. Maintain Visual Hierarchy: Use typography and spacing consistently
  3. Follow Platform Conventions: Respect platform-specific interaction patterns
  4. Test Across Devices: Ensure patterns work on all target devices

Performance Considerations

  1. Lazy Load: Use lazy loading for non-critical content
  2. Optimize Images: Use appropriate image formats and sizes
  3. Minimize Reflows: Avoid layout thrashing with proper CSS
  4. Progressive Enhancement: Build for the lowest common denominator first

Accessibility

  1. Keyboard Navigation: Ensure all patterns are keyboard accessible
  2. Screen Reader Support: Use proper ARIA labels and semantic HTML
  3. Focus Management: Provide clear focus indicators and logical tab order
  4. 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');
  });
});