Spirit Airlines

Rebuilding the flight-booking journey.

I led frontend architecture, owned booking and ancillary flows, and co-built the shared React and TypeScript component library.

My role
Senior Frontend Engineer
Period
Aug 2021 — Nov 2022
Project type
Team project

What I built

  1. Booking and checkout flows

    Built the homepage booking widget and owned fare display, baggage, seat selection, bundles, and checkout.

  2. Shared components and contracts

    Co-built reusable UI components and defined TypeScript contracts from backend handlers to the interface.

  3. Performance and accessibility

    Prioritized the booking widget, lazy-loaded deeper flows, and built keyboard and screen-reader support into the funnel.

Project outcome

The booking widget was built to a sub-300ms render budget. Shared components reduced ancillary feature-delivery time by about 30%.

Try the demo
Portfolio recreation with sample routes and fares.

Keep booking choices consistent

The booking widget, fare chips, and bundle cards share the same component library, keeping selection and pricing states consistent across the journey.

Interface preview

Spirit Airlines

Recreated airline booking interface with route inputs and fare comparisons

Portfolio recreation with sample routes and fares.

Project context

The booking journey needed a consistent frontend across flight search, fares, bags, seats, bundles, and checkout.

Try the demo
How I built it

Project details

Role
Senior Frontend Engineer
Dates
Aug 2021 – Nov 2022
Location
Fort Lauderdale, FL
Domain
Airline e-commerce / consumer

Tools & technologies

React 18 · TypeScript · Tailwind CSS · NgRx · AWS Lambda · AWS API Gateway · Jest · WCAG 2.1

Project facts

Widget render budget
Sub-300ms — Homepage booking widget held to a strict budget — critical-render priority, deep checkout modules lazy-loaded.
Faster ancillary delivery
30% — Shared TypeScript component library — booking widget, fare chips, seat map, baggage selector, bundle cards consumed across promotions, search, booking and checkout.
Type contracts
End-to-end — Lambda handler signatures shared through to UI prop types — runtime checkout errors caught at compile time, not by customers.
Accessibility
WCAG 2.1 — Keyboard navigation and screen-reader support across the entire purchase experience — built in from the start, not retrofitted.

Interface notes

  • Shared library across every surface

    Booking widget, fare-display chips and bundle cards in this view are the same components consumed by promotions, search results, and checkout summary — one source of truth.

  • Typed end-to-end to the API edge

    Every prop in this view traces back to a TypeScript contract shared with the Lambda handler that serves the fare data. A response-shape change is a compile error, not a production undefined.

  • Sub-300ms widget, lazy-loaded checkout

    The booking widget on top is critical-render priority. Deep checkout modules (seat selection, baggage details, payment) load on demand so they don't block the homepage paint.

The funnel where the revenue actually lives

Spirit Airlines is a low-cost carrier. The base fare is a teaser; the actual revenue comes from ancillaries — checked bags, carry-on bags, seat selection, bundles, priority boarding, change fees. The booking funnel is where every one of those upsells happens, and where each one either lands or doesn't. The team that owns it has to ship ancillary feature experiments fast, measure the conversion delta cleanly, and never regress the base purchase flow under all the experimentation.

The legacy stack underneath was the bottleneck. Untyped APIs at the Lambda edge meant production runtime errors in checkout — the most expensive place in the funnel to have them. Per-team component duplicates of the booking widget, fare chips, and bundle cards drifted out of sync, so the same change had to ship five times across promotions, search and checkout. No clean lazy-loading boundary between the homepage widget and the deep checkout modules meant the widget paid for code paths it didn't use. Accessibility was retrofitted after the fact, painfully and incompletely.

Replacing all of that on a revenue-critical funnel while keeping the funnel live for the airline's daily booking traffic — that's the work.

Base fare is a teaser; the actual revenue comes from ancillaries — and the funnel is where every one of those upsells either lands or doesn't.— The defining constraint

Four calls that shaped the rebuild

Building a shared component library, typing the Lambda boundary end-to-end, keeping the existing state machine through migration, and getting accessibility right from day one — each one trades short-term complexity for long-term resilience on a funnel that ships ancillary experiments constantly.

Shared TypeScript component library over per-team duplicates

The booking widget appears on the homepage, in search results, in fare display, in checkout summary. Fare-display chips render in promotions, in search, in the funnel itself. Baggage selector, seat map, bundle cards — all of them get consumed by multiple surfaces. The shared TypeScript component library is one source of truth so ancillary teams ship a feature change once and it appears everywhere consistently. Ancillary feature delivery time dropped roughly 30% after migration.

Tradeoff: Coordinating a shared library across multiple consuming teams takes governance. In exchange, revenue-critical surfaces stay visually consistent and a feature change ships once, not five times.

End-to-end TypeScript contracts at the Lambda boundary

The previous untyped API edge was the source of multiple runtime type errors in checkout — the most expensive place in the funnel to have them. A Lambda handler quietly dropped a field from its response once and the UI silently rendered undefined in a fare-display chip on the live homepage. The fix wasn't a defensive null guard; it was end-to-end TypeScript contracts from the Lambda handler signature through to UI prop types. A contract change anywhere in the chain becomes a compile-time error before it can ship to production.

Tradeoff: Shared type contracts mean coordination between Lambda and UI teams on every API change. In exchange, the entire class of “undefined silently rendered in production” bugs is gone.

Keep the existing state library through the migration

The booking-flow state machine is genuinely complex — multi-step form, conditional pricing, real-time fare updates, ancillary cross-sells. The team had deep muscle memory with the existing state library from the prior Angular era. Switching to a different state library mid-migration would have meant a parallel learning curve at the worst possible time. The booking flow kept its existing state machine; newer modules adopted Redux Toolkit and TanStack Query as the team transitioned.

Tradeoff: Two state-management patterns coexisting in the same codebase isn't a long-term architecture. In exchange, the migration shipped on schedule and the booking-flow team didn't lose velocity learning new patterns mid-flight.

Accessibility from the start, not retrofitted

A multi-step purchase form with conditional fields, dynamic pricing and ancillary cross-sells is one of the hardest things to retrofit for accessibility. We built it in from day one — keyboard navigation, screen-reader announcements on state changes, focus management across step transitions, sufficient contrast across every surface — so the end-to-end purchase experience works for assistive technology without a separate accessibility sprint after launch.

Tradeoff: Accessibility-first slows the first wireframe-to-prototype loop slightly. In exchange, the funnel ships compliant on day one instead of requiring a remediation pass post-launch.

The funnel, the shared library, and the type contracts

I built the homepage booking widget (sub-300ms render budget on the highest-traffic surface, critical-render priority on the entry path, deep checkout modules lazy-loaded with React.lazy + Suspense). I owned the baggage selection flow, seat selection with its custom seat-map renderer, the bundles upsell flow, fare display with conditional pricing and dynamic ancillary cross-sells, and the checkout path with end-to-end TypeScript contracts from the Lambda handler signature through to UI prop types.

I co-built the shared TypeScript component library that ancillary teams consume across promotions, search, booking and checkout — one source of truth that cut ancillary feature delivery time about 30%. WCAG 2.1 accessibility (keyboard navigation, screen-reader announcements on state changes, focus management across step transitions, sufficient contrast across every surface) was built in from the start across the entire funnel, not retrofitted after launch. Jest + React Testing Library coverage on every consuming component, Lighthouse CI gating PR merges so the render-budget commitment stays enforced as the codebase grows.

Scope

  • React 18 + TypeScript rebuild of homepage booking widget (sub-300ms render budget on highest-traffic surface)
  • Shared TypeScript component library: booking widget, fare-display chips, seat map, baggage selector, bundle cards
  • Fare display with conditional pricing and dynamic ancillary cross-sells
  • Baggage selection flow
  • Seat selection with custom seat-map renderer
  • Bundles upsell flow
  • Checkout with end-to-end TypeScript contracts to AWS Lambda
  • WCAG 2.1 accessibility — keyboard navigation, screen-reader announcements, focus management across step transitions
  • Performance: React.lazy + Suspense for deep checkout modules, targeted memoization on the seat-map, Lighthouse CI gating PR merges
  • Jest + React Testing Library coverage across the funnel