Color Palette

All colors are defined as HSL CSS custom properties in globals.css and consumed via Tailwind's theme extension. The application is light-mode only in production.

Semantic Colors
backgroundhsl(220,20%,95%)
foregroundhsl(222.2,84%,4.9%)
cardhsl(0,0%,100%)
primaryhsl(195,20%,60%)
secondaryhsl(210,40%,96.1%)
mutedhsl(210,40%,96.1%)
muted-foregroundhsl(215.4,16.3%,46.9%)
accenthsl(210,40%,96.1%)
destructivehsl(0,84.2%,60.2%)
border / inputhsl(214.3,31.8%,91.4%)
ringhsl(220,13%,69%)
Sidebar Colors
sidebar-backgroundhsl(222,47%,11%)
sidebar-foregroundhsl(210,40%,98%)
sidebar-accenthsl(217.2,32.6%,17.5%)
sidebar-accent-fghsl(210,40%,98%)
Brand & Functional Colors
Brand Orange#F97316
Brand Green#43A047
submit-flightrgb(120,162,181)
Expired Badge#ef4444
Expiring Soon#facc15
yellow-100Stat icon bg
green-100Stat icon bg
orange-100Stat icon bg
Chart Colors

Defined as CSS variables, used by Recharts via the chart.tsx wrapper.

chart-1--chart-1
chart-2--chart-2
chart-3--chart-3
chart-4--chart-4
chart-5--chart-5

Typography

Font Families
Body — PT Sans
The quick brown fox
The quick brown fox
font-family: 'PT Sans', sans-serif
Tailwind: font-body
Weights: 400, 700
Headline — Space Grotesk
The quick brown fox
The quick brown fox
font-family: 'Space Grotesk', sans-serif
Tailwind: font-headline
Weights: 400, 700
Type Scale
text-xs
10px
Caption text, sidebar section labels, icon labels
text-sm
13px
Body text, table cells, form labels, descriptions
text-base
14px
Default body text, paragraph content
text-lg
16px
Dialog titles, page titles (mobile), card titles
text-xl
18px
Page titles (desktop), section headings
text-2xl
20px
Card titles, stat values
text-3xl
24px
Login page logo fallback heading

Radius & Shadows

Border Radius
sm — 0.25rem (4px)
rounded-sm
md — 0.375rem (6px)
rounded-md
lg — 0.5rem (8px)
rounded-lg
--radius
full — 999px
rounded-full
badges, avatars
Shadows
shadow-sm
Cards, inputs
shadow
Dropdowns
shadow-md
Popovers
shadow-lg
Org card hover
Dialogs

Button

Wraps @radix-ui/react-slot for polymorphic rendering. Variants and sizes managed with class-variance-authority.

Variants
variant: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"
Sizes
size="lg"
h-11 px-8
size="default"
h-10 px-4
size="sm"
h-9 px-3
size="icon"
h-7 w-7
Disabled State
disabled:pointer-events-none disabled:opacity-50
Base Classes
inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0

Badge

No Radix primitive. Simple inline-flex pill with border and variant classes.

Variants
Default Secondary Destructive Outline
variant: "default" | "secondary" | "destructive" | "outline"
Sidebar Nav Badges (custom colors)
3 Expired 2 Expiring
Expired: bg-[#ef4444] text-white
Expiring: bg-[#facc15] text-[#713f12]
Base Classes
inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2

Input / Textarea

Native HTML elements. No Radix primitive. Styled to match design tokens.

Input States
Default
Disabled
Error State
flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50
Error: border-destructive
Textarea
Same classes as Input with height: auto and resize: vertical
Required Field Pattern
<span className="text-destructive">*</span>

Select

Wraps @radix-ui/react-select.

Anatomy
Trigger
Select an option
Content (Popover)
Active option
Second option
Third option
Disabled option
Trigger: flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm
Item: relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent

Checkbox

Wraps @radix-ui/react-checkbox.

Base: peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background
Checked: data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground

Card

Composable div-based component. No Radix primitive. Five sub-components: Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter.

Asset Inspection Report
Review the details for the selected asset and submit your findings.
Serviceable
Card: rounded-lg border bg-card text-card-foreground shadow-sm
CardHeader: flex flex-col space-y-1.5 p-6
CardTitle: text-2xl font-semibold leading-none tracking-tight
CardDescription: text-sm text-muted-foreground
CardContent: p-6 pt-0
CardFooter: flex items-center p-6 pt-0

Alert

No Radix primitive. Two variants: default and destructive.

ℹ️
Heads up!
This action will notify all users in the organisation via email.
⚠️
Verification Required
Your session has expired. Please sign in again to continue.
Base: relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg~*]:pl-7
Default: bg-background text-foreground
Destructive: border-destructive/50 text-destructive

Spinner

Simple CSS animation. Used in loading states across the app.

h-12 w-12 animate-spin rounded-full border-4 border-solid border-primary border-t-transparent

Toast

Built on @radix-ui/react-toast. Two variants. Positioned bottom-right on desktop, top on mobile.

Variants
Report saved successfully
Your hazard report has been submitted.
Error
Failed to upload image. Please try again.
Default: border bg-background text-foreground
Destructive: destructive group border-destructive bg-destructive text-destructive-foreground
Position (desktop): fixed bottom-0 right-0 z-[100] p-4 md:max-w-[420px]
Open: animate-in slide-in-from-bottom-full | Close: animate-out fade-out-80 slide-out-to-right-full

Dialog

Built on @radix-ui/react-dialog. Used for profile edit, org switching, red-tag notices, image galleries.

Edit Profile
Update your personal information and account settings.
Overlay: fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out
Content: fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 sm:rounded-lg
Title: text-lg font-semibold leading-none tracking-tight
Description: text-sm text-muted-foreground

Table

Semantic HTML table wrapper. No Radix primitive. Eight sub-components.

Registration Aircraft Type Organisation Status Actions
ZS-OAB Boeing 737-800 CrossChecks Aviation Active
ZS-JFA Cessna 172 CrossChecks Aviation Expired
ZS-MNB Airbus A320 Coastal Air Expiring
TableHead: h-12 px-4 text-left align-middle font-medium text-muted-foreground border-r last:border-r-0
TableCell: p-4 align-middle border-r last:border-r-0
TableRow: border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted

Form Field

Composed of FormItem, FormLabel, FormControl, FormDescription, FormMessage. Integrates with react-hook-form and zod.

As it appears on your official ID.

Please enter a valid email address.

FormItem: space-y-2
FormLabel (error): text-destructive
FormDescription: text-sm text-muted-foreground
FormMessage: text-sm font-medium text-destructive

Signature Pad

Canvas-based signature input using react-signature-canvas. Used in hazard/incident/accident reports.

Sign here
Container: relative border rounded-md
Canvas: w-full h-[200px] rounded-md bg-card
Clear button: absolute bottom-2 right-2 (outline variant, sm size)

Rich Text Editor

Wraps react-quill with the Snow theme. Used in the Notices module for formatted notice content.

Start typing your notice content here…
Library: react-quill + quill.snow.css
Toolbar modules: headers, bold, italic, underline, strike, list, link, clean
Min height: .ql-editor { min-height: 150px } (globals.css)

Multi-Select Checkbox

Custom component. Trigger button opens a Radix Popover containing checkboxes. Used for assigning roles and selecting multiple items.

2 roles selected
Pilot in Command
Safety Officer
Ground Crew
Dispatcher
Trigger: variant="outline" w-full justify-start h-10 font-normal
Popover content: p-0
Item: p-2 flex items-center cursor-pointer | Selected: bg-accent
Checkbox: mr-2 (Radix Checkbox)

File Input

Custom styled file picker. Real input is sr-only; visual trigger is a styled div.

No file selected
Choose file…
File selected
📄 report_photo.jpg
Wrap: flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm cursor-pointer hover:bg-accent
Actual input: sr-only
File icon color: text-green-500 when file selected

Image Gallery Dialog

Dialog with upload section and responsive image grid. Used for attaching photos to reports and assets. Max dialog width sm:max-w-[800px].

Report Images
Upload and manage photos for this report
Choose image…
Grid: grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-4
Item: cursor-pointer relative aspect-square hover:ring-2 hover:ring-primary
Max height content: max-h-[60vh] overflow-y-auto

App Shell

The main app shell is a CSS Grid with a collapsible sidebar. The sidebar persists its state in a browser cookie (sidebar_state, 7-day expiry). Toggle with Ctrl/⌘+B.

CrossChecks AOC
Main
DASHBOARD
ORGANISATION
USERS
Content
NOTICES
AOC ROLES
DASHBOARD
Total Organisations
12
Active Users
84
Open Reports
7
Expanded (Desktop)
grid-template-columns: 280px 1fr
md:grid-cols-[280px_1fr]
Collapsed (Icon-only)
grid-template-columns: 80px 1fr
md:grid-cols-[80px_1fr]
Mobile (Sheet Drawer)
Uses Radix Sheet
Width: 288px (18rem)
Hidden: md:hidden
Header
h-16 sticky top-0 z-10
bg-white border-b
Logo centered (org view)

Sidebar Navigation

Admin View
Organisation View
Active state: data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium
Hover: hover:bg-sidebar-accent/50 hover:text-primary
Collapsed tooltip: via Radix Tooltip on SidebarMenuButton
Sub-items: mx-3.5 border-l border-sidebar-border px-2.5 py-0.5
Collapsible Group Behavior

Nav items with subItems render as a Collapsible from @radix-ui/react-collapsible. Plain items render as a <Link>. In the Org view, COMPANY (Organisation, Users, Assets) and ADMIN (General Masters) are the two collapsible groups.

Expanded (defaultOpen when child is active)
Sidebar collapsed — groups show tooltip only

When the sidebar is collapsed to icon-only (80px), the ChevronDown is hidden. The group trigger shows only the icon with a Radix Tooltip on hover (side="right") revealing the group label.

Open/Close Logic
  • defaultOpen = true when any subItem.href matches the current pathname
  • Click the trigger row to toggle open/closed
  • No accordion behaviour — multiple groups can be open simultaneously
  • State is not persisted (resets on navigation)
Active & Hover States
  • Trigger row: text-primary when any child is active
  • Trigger hover: hover:bg-sidebar-accent/50 hover:text-primary
  • Sub-item active: bg-sidebar-accent text-primary
  • Sub-item hover: same as trigger hover
Layout & Indentation
Trigger: flex items-center justify-between gap-3 rounded-lg px-4 py-3
Content wrapper: flex flex-col gap-1 pl-11 py-2 pr-4
Sub-item link: flex items-center gap-3 rounded-lg px-3 py-2 text-sm
Collapsed Sidebar (80px)
Chevron: hidden when isCollapsed
Label text: hidden when isCollapsed
Tooltip (Radix, side="right"): shows group label
Sub-items: still render inside CollapsibleContent but labels are hidden — only icons visible
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"

<Collapsible defaultOpen={isClient && item.subItems.some(sub => pathname.startsWith(sub.href))}>
  <CollapsibleTrigger> … trigger row with icon + label + ChevronDown … </CollapsibleTrigger>
  <CollapsibleContent>
    <div className="flex flex-col gap-1 pl-11 py-2 pr-4">
      {item.subItems.map(sub => <Link … className="… bg-sidebar-accent text-primary (if active)" />)}
    </div>
  </CollapsibleContent>
</Collapsible>

Page Patterns

Stat Card Grid (Dashboard)
🏢
12
Total Organisations
👤
84
Active Users
✈️
7
Open Reports
Grid: grid gap-6 md:grid-cols-2 lg:grid-cols-3
Card header: flex flex-row items-center justify-between space-y-0 pb-2
Icon: rounded-full p-1 h-8 w-8 (coloured bg: bg-yellow-100 text-yellow-500 etc.)
Value: text-2xl font-bold
List Page (Table + Toolbar)
NameCategoryExpiryStatusActions
Boeing 737-800Aircraft2025-12-01 Active
Toolbar: flex items-center justify-between p-4 border-b
Search + filters left, actions (Export, Add) right
Table: full-width with custom TableHead/TableCell border-r styling
Form Page (Add / Edit)

Add New Asset

Fill in the details below to register a new asset.

Select category
Pattern: Card > CardHeader + CardContent (grid grid-cols-2 gap-4) + CardFooter
Form managed by react-hook-form + Zod schema
Save triggers POST /api/{slug}/{collection}/ — body validated against collection schema_json
Login Page Layout
CrossChecks AOC
CrossChecks AOC Login
Current Notices
System maintenance scheduled for Sat 08:00–10:00 UTC.
Secure · Compliant · Aviation Safety Management System
Layout: w-full lg:grid lg:min-h-screen lg:grid-cols-2
Left: flex items-center justify-center — form centered, mx-auto max-w-sm
Right: hidden lg:flex bg-primary/5 — notices + benefits panel
Logo: bg-sidebar p-4 rounded-md (uses sidebar dark bg)

Icon Library

All icons are from Lucide React (lucide-react@^0.475). Default size in components: [&_svg]:size-4 (16px). Sidebar icons: 14–16px.

LayoutGrid
Building2
Users
Bell
Plane
KeyRound
Settings
AlertTriangle
FileText
ClipboardList
Grid
BarChart
Search
Trash2
Edit
Eye
UserPlus
Trash
Download
ChevronDown
Menu
X
Shield
Lock
Import: import { IconName } from 'lucide-react'
Version: lucide-react@^0.475.0
Default SVG size in Button: [&_svg]:size-4 [&_svg]:shrink-0

Responsive Breakpoints

Standard Tailwind breakpoints. The app is primarily a desktop application — mobile support is available via Sheet sidebar and single-column layouts.

Mobile
< 640px
Single column, Sheet sidebar, text-lg page titles, toast slides from top
sm
≥ 640px
sm:p-12 login, sm:rounded-lg dialog, toast slides from bottom
md
≥ 768px
Fixed sidebar visible, 2-col grids (md:grid-cols-2), page title text-xl, sidebar 280px
lg
≥ 1024px
3-col stat grids (lg:grid-cols-3), lg:p-6 spacing, login 2-col layout (lg:grid-cols-2)
Key Responsive Patterns
Sidebar: hidden md:block (fixed) · Sheet on mobile (md:hidden trigger)
Stat cards: grid md:grid-cols-2 lg:grid-cols-3
Form grids: grid-cols-1 md:grid-cols-2
Login: w-full lg:grid lg:grid-cols-2 lg:min-h-screen
Page title: text-lg md:text-xl
Content padding: p-4 lg:p-6 gap-4 lg:gap-6
Image gallery grid: grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6
Mobile breakpoint in JS: useIsMobile() → 768px (matches Tailwind md)
CrossChecks AOC Design System · Generated 2026-04-07 · Next.js 15.3 + Tailwind CSS 3.4 + Radix UI + Nanobase