RAPID · AIRCRAFT PERFORMANCE

Design System

The single source of truth for Rapid across pilot mobile and tablet. Built on the shadcn/ui component architecture and token conventions, themed with the Rapid brand — IBM Plex Sans/Mono and the cockpit-navy palette. Tailwind class names map directly to the CSS variables documented here.

{{ t }}
{{ m.n }}
{{ m.label }}
FOUNDATIONS

Color & tokens

Semantic tokens follow shadcn/ui naming exactly — background / foreground pairs, primary, muted, accent, border / input / ring. Reference the variable, never the hex. Toggle the theme (top-left) to preview both modes live.

TOKENTAILWIND / USAGEDARKLIGHT
{{ c.token }} {{ c.use }} {{ c.dark }} {{ c.light }}

Status / semantic

{{ s.token }}
{{ s.note }}

Status tints: hue at 12–14% alpha for fills, 30–40% for borders. Status is always reinforced by text or icon — never color alone (accessibility).

FOUNDATIONS

Typography

Aa
IBM Plex Sans
UI · headings · body · labels · 400/500/600/700
0.9
IBM Plex Mono
All numeric & aviation data — V-speeds, masses, METAR, ICAO
TOKENSIZE / WEIGHT / LHSPECIMEN
{{ t.token }} {{ t.spec }} {{ t.sample }}
FOUNDATIONS

Spacing, radius & elevation

Factor 4 — every token equals its step number × 4px (--space-N = N × 4px). Every padding, margin, and gap must use a --space-* token — never a hardcoded pixel value. Base radius token --radius: 12px.

/* correct */   gap: var(--space-3);   padding: var(--space-4) var(--space-6);
/* wrong   */   gap: 12px;             padding: 16px 24px;

SPACING

{{ sp.token }}

RADIUS

{{ r.label }}

ELEVATION

shadow-smrows, chips
shadow-mdcards, popovers
shadow-lgdialogs, sheets
LIBRARY

Components

Every component is a Rapid-themed shadcn/ui primitive. Each entry below shows live variants, a props reference, and the shadcn/Tailwind implementation. Interactive playgrounds live in the Storybook.

Button

<Button variant size />

Primary actions use the high-contrast brand fill. Variants: default · secondary · ghost · destructive · outline. Min height 44px on touch surfaces. Loading swaps the label for a spinner and disables the control.

VARIANTS
SIZES · STATES
<Button variant="default" size="lg">Calculate</Button>
<Button variant="secondary">Add MEL</Button>
<Button variant="destructive" size="icon">
  <Trash2 />
</Button>
<Button disabled>{loading && <Spinner/>} Save</Button>
PROPVALUES
variantdefault · secondary · outline · ghost · destructive
sizesm · default · lg · icon
disabledboolean

Input & field

<Label/> + <Input/>

A field = label + control + helper. Numeric/aviation values use the mono face and a unit suffix. Helper text carries the valid range (copied identically across mobile & tablet). Error state swaps the border and helper to --destructive.

Flight number
e.g. SJ1842
Temp
range -54–+55 °C · focused
Gross weight
kg
range 34000–61234 · step 1
QNH
out of range · 930–1070 hPa
Search airport
search variant
Disabled (METAR auto)
360 / 06 kt
read-only · filled from METAR
<div className="space-y-2">
  <Label htmlFor="gw">Gross weight</Label>
  <div className="flex items-center gap-2">
    <Input id="gw" inputMode="numeric" className="font-mono" />
    <span className="text-muted-foreground">kg</span>
  </div>
  <p className="text-xs text-muted-foreground">range 34000–61234</p>
</div>

Select & dropdown

<Select/> · <DropdownMenu/>

Trigger shows the current value + chevron. The popover lists options with a check on the selected row; hover uses --accent. Used for Flaps, Pack, ENG AIL, Runway condition, NOTAM runway.

Runway condition
Open popover
Dry
Wet
Slush
Stand Water
Dry Snow
<Select value={cond} onValueChange={setCond}>
  <SelectTrigger><SelectValue/></SelectTrigger>
  <SelectContent>
    {["Dry","Wet","Slush","Stand Water","Dry Snow"].map(o =>
      <SelectItem key={o} value={o}>{o}</SelectItem>)}
  </SelectContent>
</Select>

Toggles & choice

Switch · Tabs(segmented) · RadioGroup

Switch for on/off prefs (keep-awake, biometric). Segmented control for 2–3 mutually-exclusive options (theme, units, density). Radio rows for longer single-select lists (language).

SWITCH
Auto-load METAR
Reduce motion
SEGMENTED
RADIO
English (UK)
Deutsch
Français
<Switch checked={autoMetar} onCheckedChange={setAutoMetar} />
<Tabs value={theme} onValueChange={setTheme}>
  <TabsList><TabsTrigger value="dark">Dark</TabsTrigger>…</TabsList>
</Tabs>

Badge & status

<Badge variant />

Status pills always pair a tint fill with a text label — never color alone. Runway result status (OK / EXCD), update state, NOTAM/MEL chips, and AIRAC currency all use this primitive.

OK EXCD STALE NOTAM ×2 MEL ×1 Up to date AIRAC 2608

Card & list row

<Card/> · row

Cards group related data on --card. List rows (aircraft, airports, settings) share a leading icon · title + meta · trailing control/chevron layout with 44px+ height.

EP-FSICurrent
737-500 · v1.0.2
AIRAC 2608-02 · to 21 Jul
Manchester — EGCCDeparture · elev 257 ft
GeneralApp behaviour & startup

Tabs & navigation

bottom-nav · side-rail · tabs

Mobile uses a bottom tab bar; tablet uses a persistent side rail. Same destinations, same icons — layout adapts, vocabulary doesn't.

Home
Takeoff
Flights
Settings

Dialog & sheet

<AlertDialog/> · <Sheet/>

Destructive actions use an AlertDialog (cancel + confirm) gated by the “Confirm before delete” preference. Bottom sheets host the airport picker, MEL list, and NOTAM list on mobile; tablet uses a centered dialog.

Delete NOTAM?
A1234 · RWY 27
Removes it from the current flight and recomputes affected runway results. This cannot be undone.
Select departure airport
EGCCManchester AirportManchester, UK

Toast

toast(message)

Transient confirmation, ~2.4s auto-dismiss, bottom-centered. Used after add/delete/select actions and async results.

EGCC set as departure airport
NOTAM removed

Table

<Table/> · runway results

Data tables use the mono face for all numeric columns and a status badge per row. Rows are tappable to open runway detail.

RwyT/O LimitSTS
0562 480 kgOK
27 ·A123440 095 kgEXCD

Checkbox

<Checkbox/>

Boolean field for multi-select forms. Always paired with a visible label; minimum 44 px touch target. Indeterminate state for partial-select headers.

<div className="flex items-center gap-3">
  <Checkbox id="ep-fsi" checked={checked} onCheckedChange={setChecked} />
  <Label htmlFor="ep-fsi">EP-FSI</Label>
</div>

Avatar

<Avatar/> · AvatarFallback

User identity mark. Displays a profile image with an initials fallback. Status dot overlays the bottom-right corner: success = online, warning = busy.

sm · 32px
HK
md · 40px
HK
lg · 48px
HK
no image
<Avatar>
  <AvatarImage src={profileUrl} alt="HK" />
  <AvatarFallback>HK</AvatarFallback>
</Avatar>

Chip & tag

inline · removable · filter

Compact inline labels for active filters and selected items. Removable chips dismiss a single selection with an ×. Used for MEL and NOTAM inclusions on the takeoff form.

Filter NOTAM active MEL ×1 Slush Selected
<Badge variant="info" className="rounded-full">NOTAM active</Badge>
<Badge variant="warning" className="rounded-full flex gap-1">
  MEL ×1 <X size={10} className="cursor-pointer" />
</Badge>

Progress bar

<Progress value />

Linear indicator for long-running operations. Determinate shows a fill; indeterminate uses the shimmer sweep. Used in the update-flow progress per aircraft and AIRAC cycle.

EP-FSI · updating72%
AIRAC 2609-01Done
EP-GHI · connecting
<Progress value={72} />
<Progress value={100} className="[&>div]:bg-success" />
<Progress />  {/* indeterminate */}

Accordion

<Accordion/> · collapsible

Disclosure sections with a chevron trigger (min 44 px). Used in the runway info panel — CONDITIONS, CONFIGURATION, LIMITATION, OPERATIONAL, OBSTACLES, MEL/CDL each collapse to save vertical space.

CONDITIONS
Wind
360 / 06 kt
OAT
+18 °C
QNH
1013 hPa
Rwy cond
Dry
CONFIGURATION
LIMITATION
<Accordion type="single" collapsible>
  <AccordionItem value="conditions">
    <AccordionTrigger>CONDITIONS</AccordionTrigger>
    <AccordionContent>Wind · OAT · QNH · Rwy cond</AccordionContent>
  </AccordionItem>
</Accordion>

FAB

Floating Action Button

Persistent elevated CTA for the most frequent action on a screen. Mobile Flights list: "New flight". Floats bottom-right above the tab bar. Extends to a labelled pill when space allows.

<Button variant="default" size="icon"
  className="rounded-full h-12 w-12 shadow-md fixed bottom-24 right-6">
  <Plus />
</Button>

METAR display

MetarCard · parsed

Aviation weather block. Raw METAR string in mono type followed by parsed fields. Wind, visibility, OAT, QNH, and ceiling feed directly into the performance calculation.

EGCC250820Z
LIVE
EGCC 250820Z 36006KT 9999 FEW030 BKN075 18/10 Q1013 NOSIG
WIND
360 / 06
kt
VIS
9999
m
QNH
1013
hPa
OAT
+18
°C
DP
+10
°C
CEILING
BKN075
ft
<MetarCard icao="EGCC"
  raw="EGCC 250820Z 36006KT 9999 FEW030 BKN075 18/10 Q1013"
  parsed={parsedMetar} />

V-speed strip

VSpeedStrip · V1 VR V2

Primary output of a takeoff calculation. V1, VR, V2, VFTO, and N1 in large mono type with per-runway status badge. EXCD variant highlights exceeded weights in destructive.

RWY 05 · T/O LIMITOK
V1
138
VR
142
V2
148
VFTO
165
N1
90.4
62 480 kg · Flaps 5 · Improved · +18 °C
<VSpeedStrip runway="05" status="ok"
  v1={138} vr={142} v2={148} vfto={165} n1={90.4}
  weight={62480} flaps={5} />

Runway diagram

RunwayDiagram · compass

Compass rose with runway orientation and wind arrow. Aircraft icon aligned to the departure threshold. Provides immediate spatial context for wind cross/tail component.

N E S W 05 23 360/06
<RunwayDiagram
  runwayHeading={50} windDirection={360} windSpeed={6}
  activeRunway="05" />
MOLECULES

Molecular components

Composed of two or more atoms. Each is a self-contained functional unit with its own props contract.

MOLECULE

Segmented control

<SegmentedControl options value />

Connected pill-group for mutually exclusive options. Used for TAKEOFF/LANDING and OAT/ASSTEM switches in the pilot apps. Visually joined segments, selected segment is filled.

<SegmentedControl
  options={["TAKEOFF","LANDING"]}
  value={mode}
  onChange={setMode}
/>
PROPVALUES
optionsstring[]
valuestring
onChange(v: string) => void
classNamestring?
MOLECULE

Combobox

<Combobox placeholder options />

Search input with live-filtered results list. Used for airport ICAO search and aircraft registration search. Composed of an Input atom + a popover results list — distinct from Select (no free-text entry in Select).

EGLL
EGLLLondon Heathrow
EGKKLondon Gatwick
EGSSLondon Stansted
<Combobox
  placeholder="Search ICAO…"
  options={airports}
  onSelect={(icao) => setAirport(icao)}
/>
// Built on shadcn Command + Popover
PROPVALUES
placeholderstring
options{ value, label }[]
onSelect(v: string) => void
classNamestring?
MOLECULE

Tab strip

<TabStrip tabs activeTab />

Row of underline-style tabs for admin-surface navigation (messages, company profile sections, data management). Sits on a border-bottom container; each tab uses the TabUnderline atom.

<TabStrip
  tabs={["Contracts","Aircraft","Updates","Users"]}
  activeTab={active}
  onChange={setActive}
/>
PROPVALUES
tabsstring[]
activeTabstring
onChange(t: string) => void
classNamestring?
MOLECULE

Bottom sheet

<BottomSheet open title />

Slide-up modal panel from the bottom of the screen. Primary modal pattern on mobile (vs. centered dialog on tablet/admin). Drag handle, backdrop overlay, animated entry.

Add NOTAM
<BottomSheet open={open} onClose={() => setOpen(false)}
  title="Add NOTAM">
  {/* content */}
</BottomSheet>
// Mobile only — tablet uses Dialog
PROPVALUES
openboolean
onClose() => void
titlestring?
snapPointsnumber[]?
MOLECULE

KPI card

<KpiCard value label variant />

Single metric stat card showing a numeric value, label, optional icon, and semantic color variant. Used in admin dashboard grids and the super-admin monitoring panel.

Active aircraft
47
Across all fleets
Expiring soon
3
Subscriptions <30 days
Uptime
99.8%
Last 30 days
<KpiCard value="47" label="Active aircraft"
  hint="Across all fleets" variant="info" />
<KpiCard value="3" label="Expiring soon"
  variant="warn" />
PROPVALUES
valuestring | number
labelstring
hintstring?
variant"default" | "info" | "warn" | "error" | "ok"
iconReactNode?
MOLECULE

Feed item

<FeedItem icon title body time />

Single timestamped activity event row used in notification inboxes and audit logs. Unread state adds --info left border and bold title. Icon slot accepts any status icon.

New contract ready for review09:14
SkyJet Europe submitted a new contract. Awaiting Rapid approval.
AIRAC 2607-01 publishedYesterday
Pushed to 12 companies. All acknowledged.
<FeedItem
  icon={<FileIcon />} variant="info"
  title="New contract ready for review"
  body="SkyJet Europe submitted…"
  time="09:14" unread />
PROPVALUES
titlestring
bodystring
timestring
iconReactNode
variant"default" | "info" | "warn" | "error" | "success"
unreadboolean?
MOLECULE

OTP step

<OtpStep step label done />

Numbered signing step card used in the e-signature flow. Two states: pending (shows digit inputs) and done (shows checkmark). Each step collects a 6-digit OTP from one party.

Step 1 · Airline approval
Signed by SkyJet Europe · 14 Jul 2026
2
Step 2 · Rapid approval
Enter the 6-digit OTP sent to admin@rapid.aero
<OtpStep step={1} label="Airline approval" done
  signedBy="SkyJet Europe" signedAt="14 Jul" />
<OtpStep step={2} label="Rapid approval"
  email="admin@rapid.aero" />
PROPVALUES
stepnumber
labelstring
doneboolean?
signedBystring?
signedAtstring?
emailstring? (shown when pending)
MOLECULE

Nav item

<NavItem icon label active />

Single navigation rail item: icon + label + optional SOON badge. Used inside NavRail. Active state uses --primary background. SOON badge renders a warning-tint chip.

Home
Registers
Analytics
SOON
<NavItem icon={<Home />} label="Home" active />
<NavItem icon={<Monitor />} label="Analytics" soon />
PROPVALUES
iconReactNode
labelstring
activeboolean?
soonboolean?
onClick() => void
MOLECULE

Update item

<UpdateItem label progress status />

Single download queue row showing label, progress bar, and status. Used inside UpdateCenter. Three states: pending, downloading (with % progress), complete.

B737-800 perf file68%
AIRAC 2608-02Done
MD-90 perf fileWaiting
<UpdateItem label="B737-800 perf file" progress={68} status="downloading" />
<UpdateItem label="AIRAC 2608-02" progress={100} status="done" />
<UpdateItem label="MD-90 perf file" status="pending" />
PROPVALUES
labelstring
progressnumber (0–100)
status"pending" | "downloading" | "done" | "error"
ATOMS

Atomic components

Single-purpose, indivisible UI elements. Each exists in both the pilot apps and admin surfaces.

ATOM

Connectivity dot

<ConnectivityDot status />

Inline status chip shown in the top nav to communicate real-time network state. Uses text + icon so it is never color-only. Online = --success tint, Offline = --destructive tint.

ONLINE OFFLINE
<ConnectivityDot status="online" />
<ConnectivityDot status="offline" />
PROPVALUES
status"online" | "offline"
classNamestring?
ATOM

Callout

<Callout variant />

Inline alert banner for workflow guidance, warnings, or rejection feedback. Four semantic variants. Always uses icon + text — never color alone.

Sign this contract in both directions — Rapid and the airline must both approve.
AIRAC 2608-02 expires in 3 days. Update before the next operation.
Contract rejected: missing MEL certification for A320-200 fleet.
All performance files validated and published to SkyJet Europe.
<Callout variant="info">Sign in both directions.</Callout>
<Callout variant="warn">AIRAC expires soon.</Callout>
<Callout variant="error">Contract rejected.</Callout>
<Callout variant="success">Files published.</Callout>
PROPVALUES
variant"info" | "warn" | "error" | "success"
childrenReactNode
classNamestring?
ATOM

Tab (underline)

<TabUnderline label active />

Single tab with underline-style active indicator. Used in a row (TabStrip) for admin-surface navigation. Distinct from the pill/chip tabs used in pilot apps.

<TabUnderline label="Inbox" active />
<TabUnderline label="Tickets" />
PROPVALUES
labelstring
activeboolean
onClick() => void
classNamestring?
ATOM

OTP digit

<OtpDigit value />

Single digit input cell for 6-digit OTP entry during contract e-signature. Filled state shows the digit; empty state shows a blinking cursor placeholder.

6-DIGIT OTP ROW
3
7
4
<OtpDigit value="3" />   {/* filled */}
<OtpDigit value="" />    {/* empty */}
<OtpDigit value="" focused /> {/* active */}
PROPVALUES
valuestring (0–9 or empty)
focusedboolean
onChange(v: string) => void
ATOM

Breadcrumb

<Breadcrumb items />

Back-link or multi-level crumb navigation. Used in admin detail pages (company profile, performance drill-down) to navigate back without a full sidebar change. Chevron separator, current page non-linked.

Companies SkyJet Europe Performance history
<Breadcrumb items={[
  { label:"Companies", href:"/companies" },
  { label:"SkyJet Europe", href:"/companies/123" },
  { label:"Performance history" },
]} />
PROPVALUES
items{ label: string; href?: string }[]
classNamestring?
ATOM

Upload box

<UploadBox accept onUpload />

Drag-and-drop file upload zone with dashed border. Used for AFM performance file uploads and aircraft registration documents in the admin panel.

Drop performance file here
or browse · PDF or ZIP · max 50 MB
<UploadBox
  accept=".pdf,.zip"
  onUpload={(files) => handleUpload(files)}
  hint="PDF or ZIP · max 50 MB"
/>
PROPVALUES
acceptstring (MIME / extensions)
onUpload(files: FileList) => void
hintstring?
disabledboolean?
ATOM

Subscription ring

<SubscriptionRing pct days />

SVG donut ring showing subscription days remaining as a percentage. Displayed in the tablet top bar. Color transitions: success → warning → destructive as the subscription nears expiry.

85 %
85
22 %
22
5 %
5
<SubscriptionRing pct={85} days={312} />
{/* color: success >30%, warning 10–30%, destructive <10% */}
PROPVALUES
pctnumber (0–100)
daysnumber (remaining)
sizenumber? (default 48)
PATTERNS

UI states

Every async surface and every list must define four states. Examples below use real app screens.

EMPTY · no saved flights
No flights yet
Create a takeoff calculation to see it saved here for quick recall.
LOADING · computing performance
Calculating runway limits…
ERROR · AIRAC fetch failed
Couldn’t reach the database server
AIRAC update timed out. Check your connection — cached data (2608-02) is still in use.
SUCCESS · update complete
All aircraft up to date
10 aircraft & the airport database are now on AIRAC 2608-02.

Rule: empty states for every list (flights, NOTAMs, MELs, search results); skeletons for every async list; inline error + retry that preserves cached data; success via toast for small actions or a confirmation panel for multi-step flows.

PATTERNS

App patterns

Airport selection
Searchable picker (sheet on mobile, dialog on tablet) shared by Home and flight creation. Selection drives METAR/TAF, runway list, and NOTAM departure — single source of truth.
Settings (schema-driven)
A 9-item menu → detail sub-pages built from a row schema (toggle / segmented / radio / info / action / field). Identical structure on mobile & tablet.
Delete with confirm
NOTAM & MEL rows expose a trash action → AlertDialog (gated by the confirm-before-delete preference) → toast + list refresh + recompute.
Update flow
“Need to Update (N)” appears when aircraft or AIRAC are stale → progress per item (single/batch/AIRAC) → success panel → button flips to “Up to date”.
PRODUCT

Dev handoff

// tailwind.config / globals.css — drop-in shadcn theme
:root {
  --background:222 47% 6%;   --foreground:210 16% 84%;
  --primary:213 33% 94%;     --primary-foreground:222 50% 8%;
  --muted-foreground:208 12% 54%; --border:212 31% 16%;
  --ring:213 30% 78%;        --radius:0.7rem;
}
// then: npx shadcn@latest add button input select switch dialog ...
Naming
PascalCase components, camelCase props, kebab tokens. Mirror shadcn’s file layout (components/ui/*).
Theming
Dark is default; .light / system via next-themes. Never hard-code hex in components.
A11y
44px targets, visible focus ring, status reinforced by text/icon, WCAG AA contrast in both modes.