Skip to content
Current State: ALPHA - Use at your own risk / Work in Progress

Responsive UI SaaS-Style Patterns

This document describes the responsive design patterns and SaaS-style UI conventions used throughout Eryxon MES.

The app is designed to work equally well on all screen sizes - desktop, tablet, and mobile. It is not mobile-first or desktop-first, but rather responsive and adaptive to provide the best experience on each device.



Modern SaaS applications (Notion, Linear, Stripe Dashboard) use consistent patterns for displaying detailed information. We follow these conventions:

Instead of displaying all data in one scrollable view, content is organized into logical tabs:

┌─────────────────────────────────────────────┐
│ Operation Name Status │
│ #Part-001 · JOB-1234 │
├─────────────────────────────────────────────┤
│ [Details] [Resources (3)] [Files (2)] │ ← Underline-style tabs
├─────────────────────────────────────────────┤
│ │
│ Tab content here... │
│ │
└─────────────────────────────────────────────┘

Implementation:

<Tabs defaultValue="details" className="flex-1 flex flex-col overflow-hidden">
<TabsList className="h-10 w-full justify-start bg-transparent p-0 gap-4">
<TabsTrigger
value="details"
className="data-[state=active]:bg-transparent data-[state=active]:shadow-none data-[state=active]:border-b-2 data-[state=active]:border-primary rounded-none px-0 pb-3"
>
Details
</TabsTrigger>
{/* Additional tabs */}
</TabsList>
<div className="flex-1 overflow-y-auto">
<TabsContent value="details" className="p-4 sm:p-6 space-y-5 m-0">
{/* Content */}
</TabsContent>
</div>
</Tabs>

Modal headers display:

  • Title prominently
  • Status badge (not buried in content)
  • Breadcrumb-style context (Part # · Job #)
  • Action buttons alongside title
<div className="px-4 sm:px-6 py-4 border-b bg-muted/30">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-3">
<div className="flex-1 min-w-0">
<DialogTitle className="text-lg sm:text-xl font-semibold truncate">
{title}
</DialogTitle>
<div className="flex items-center gap-2 mt-1 text-sm text-muted-foreground">
<Package className="h-3.5 w-3.5" />
<span>#{partNumber}</span>
<span>·</span>
<span>JOB-{jobNumber}</span>
</div>
</div>
<div className="flex items-center gap-2">
<StatusBadge status={status} />
{/* Action buttons */}
</div>
</div>
</div>

Key metrics displayed in a scannable grid at the top of content:

┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ DUE DATE │ │ EST TIME │ │ MATERIAL │ │ QUANTITY │
│ Dec 15 │ │ 45 min │ │ Steel │ │ 100 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div className="p-3 rounded-lg bg-muted/50 border">
<p className="text-xs text-muted-foreground uppercase tracking-wide">
Due Date
</p>
<p className="mt-1 font-semibold text-sm">Dec 15, 2024</p>
</div>
{/* More stat cards */}
</div>

Content grouped in bordered, rounded cards with subtle backgrounds:

<div className="border rounded-lg p-4 bg-muted/20">
<h4 className="text-xs text-muted-foreground uppercase tracking-wide mb-2">
Context
</h4>
{/* Section content */}
</div>
  • Only show tabs if content exists
  • Use collapsible sections for secondary information
  • Hide less important columns on mobile (accessible via detail modals)
{filesCount > 0 && (
<TabsTrigger value="files">
Files ({filesCount})
</TabsTrigger>
)}

The useResponsiveColumns hook manages which columns are visible at different breakpoints:

import { useResponsiveColumns } from "@/hooks/useResponsiveColumns";
const { columnVisibility, isMobile } = useResponsiveColumns([
{ id: "job_number", alwaysVisible: true },
{ id: "due_date", alwaysVisible: true },
{ id: "status", alwaysVisible: true },
{ id: "flow", hideBelow: "lg" }, // Hidden on mobile/tablet
{ id: "details", hideBelow: "md" }, // Hidden on mobile
{ id: "files", hideBelow: "md" }, // Hidden on mobile
{ id: "actions", alwaysVisible: true },
]);
BreakpointWidthTypical Devices
sm640pxSmall phones
md768pxTablets
lg1024pxLaptops
xl1280pxDesktops

Pass column visibility to the DataTable component:

<DataTable
columns={columns}
data={data}
columnVisibility={columnVisibility}
pageSize={isMobile ? 10 : 20}
maxHeight={isMobile ? "calc(100vh - 320px)" : "calc(100vh - 280px)"}
/>
PriorityAlways VisibleExamples
CriticalYesID, Name, Status
ImportantShow on tablet+Due Date, Assigned To
SecondaryShow on desktopDetails, Files, Flow
ActionsYesMenu button

The UI adapts to provide optimal experiences across all device sizes.

Modals adapt their presentation based on available screen space:

Screen SizeBehavior
Mobile (<640px)Full-screen or bottom sheet style
Tablet (640-1024px)Centered modal, medium width
Desktop (>1024px)Centered modal, larger width
<DialogContent className="
sm:max-w-xl lg:max-w-2xl
max-h-[85vh]
overflow-hidden
flex flex-col
p-0
">

Adapts to screen size for optimal viewing:

<DialogContent className="
glass-card
w-full h-[100dvh]
sm:h-[90vh] sm:max-w-6xl
flex flex-col p-0
rounded-none sm:rounded-lg
inset-0
sm:inset-auto sm:left-[50%] sm:top-[50%]
sm:translate-x-[-50%] sm:translate-y-[-50%]
">

Spacing adjusts based on available screen real estate:

<div className="glass-card p-2 sm:p-4">
{/* Content */}
</div>

Interactive elements maintain adequate touch targets (44px minimum) on all devices for accessibility and usability.


ComponentFileChanges
DataTablesrc/components/ui/data-table/DataTable.tsxAdded columnVisibility prop
Dialogsrc/components/ui/dialog.tsxMobile bottom-sheet behavior
PageStatsRowsrc/components/admin/PageStatsRow.tsx2-col mobile, 4-col desktop grid
AdminPageHeadersrc/components/admin/AdminPageHeader.tsxStack on mobile
ModalFileTabs
JobDetailModalsrc/components/admin/JobDetailModal.tsxOverview, Parts, Delivery
PartDetailModalsrc/components/admin/PartDetailModal.tsxDetails, Operations, Files
OperationDetailModalsrc/components/admin/OperationDetailModal.tsxDetails, Resources, Files
HookFilePurpose
useResponsiveColumnssrc/hooks/useResponsiveColumns.tsManage column visibility by breakpoint
useBreakpointsrc/hooks/useResponsiveColumns.tsGet current breakpoint info

  1. Equal Device Support - Works equally well on desktop, tablet, and mobile
  2. Scannability - Users find information quickly via organized tabs and stat grids
  3. Focus - One context at a time via tabbed interfaces
  4. Density - Appropriate data density for each screen size
  5. Consistency - Same patterns across all modals and pages
  6. Progressive Disclosure - Show essential data first, details on demand
  7. 100% Data Coverage - All data accessible on every device, presentation adapts to screen size