Hubs, not airports
Nobody plans ski trips by airport code. It took five iterations to realize the right abstraction was hubs—Denver, Salt Lake, Reno—not regions or resorts.
Generic travel UX doesn’t work for ski trips. I learned this the hard way: by building a flat airport selector and watching it fall apart.
The problem
I built a ski trip finder that searches flights to ski destinations. The first version had a simple multi-select: pick your destination airports from a list. ABQ, ALB, ASE, BTV…
Nobody thinks this way. When I plan a ski trip, I don’t think “I want to fly to EGE.” I think “I want to go to Vail.” But it’s deeper than that—I don’t even think about individual resorts first. I think about which hub I can fly into.
Denver opens up Vail, Breckenridge, Keystone, Arapahoe Basin, Winter Park. Salt Lake opens up Park City, Brighton, Snowbird, Alta. Reno opens up Tahoe.
The hub is the trip decision. The resort is secondary.
What I tried
Flat airport list. Functional, but meaningless. Unless you’re a frequent flyer, you don’t know that EGE is Eagle County (Vail) or that HDN is Hayden (Steamboat).
Region grouping. Better, but “Colorado” spans multiple hubs. Denver, Aspen, and Durango are all Colorado, but they’re completely different trip decisions. Selecting “Colorado” pulled in every mountain airport in the state—not what anyone wanted.
Resort-first selection. Closer, but it missed the logistics. You don’t pick a resort and then figure out how to get there. You pick a hub based on flight prices and options, then decide which resort to hit once you’re there.
Where it broke down
It took five iterations to land on the right abstraction. The first attempt added a “Hubs” button that selected DEN, SLC, and RNO directly. But that only grabbed the hub airports themselves—missing all the secondary airports those hubs actually serve.
The next fix added region selection. Now clicking “Hubs” also selected regions. But the API started requesting ASE, DEN, EGE, GUC, HDN, MTJ—every Colorado airport, regardless of hub accessibility.
The problem: regions and hubs aren’t the same thing. Colorado is a region. Denver is a hub. Some Colorado airports (like ASE for Aspen) have direct flights—they’re not “Denver hub” destinations even though they’re in the same state.
The logic I needed: select the hub airports, and select regions, but only the regions those specific hubs serve.
The data model
I added a hub field to each resort:
{
"slug": "vail",
"name": "Vail",
"airport": "EGE",
"hub": "DEN",
"region": "Colorado"
}
Vail’s closest airport is EGE (Eagle County), but its hub is Denver. When you select “Denver hub,” you get all Denver-hubbed destinations—Vail, Breckenridge, Keystone, Steamboat—searched at their actual airports.
The frontend groups destinations by hub, with a “Regional” catch-all for everything else:
- Denver Hub → Vail (EGE), Breckenridge (DEN), Winter Park (DEN), Steamboat (HDN)
- Salt Lake Hub → Park City (SLC), Snowbird (SLC), Jackson Hole (JAC), Big Sky (BZN)
- Reno/Tahoe Hub → Palisades (RNO), Northstar (RNO), Heavenly (RNO)
- Regional → Mammoth (MMH), Taos (ABQ), Killington (BTV)
“Hubs Only” is a one-click quick-select that grabs the big three western hubs. For destination skiers comparing options, this is the 80% case.
Dual access: Aspen’s interesting case
Aspen has both a local airport (ASE) with direct flights and a hub relationship with Denver. Both are valid trip patterns: budget travelers drive from DEN; convenience seekers fly direct to ASE.
I mapped Aspen to the Denver hub:
{
"slug": "aspensnowmass",
"airport": "ASE",
"hub": "DEN"
}
This means Aspen shows up under “Denver Hub” even though it has its own airport. The model accommodates both—if you want direct flights, ASE is right there in the list. If you’re comparing Denver-accessible options, Aspen is included.
Intentional scope: destination skiers
The East Coast resorts (Killington, Sugarloaf, Sunday River) all land in “Regional” with no hub. That’s intentional.
This tool is for destination ski trips—flying somewhere to ski for a few days. East Coast skiing is mostly weekend-warrior territory: drive up Friday night, ski Saturday and Sunday, drive home. Different mental model, different trip planning. Mapping Burlington (BTV) as a “hub” for Vermont skiing would technically work, but it misses the point. Nobody flies to Burlington and then picks between Killington and Sugarbush based on a 45-minute drive difference.
The hub abstraction works specifically because western destination skiing works that way. You really do fly into Denver and choose between resorts. You really do weigh “SLC to Park City” vs “DEN to Vail” as comparable options.
Rough edges
Jackson Hole and Big Sky are currently mapped to the Salt Lake hub. That’s a 5-6 hour drive—not really “hub accessible” in the same way Park City is. It’s the closest major hub, but the mapping feels wrong.
This might need a fourth category: destinations with their own airports that are legitimately standalone trips (Jackson Hole, Aspen) vs. destinations that require a hub (Vail, Park City). Not there yet.
Why this matters
Google Flights doesn’t handle this. Kayak doesn’t handle this. They’re generic travel tools—they show you airports and let you figure it out. That’s fine when you’re flying to visit family. It’s insufficient when you’re trying to compare Vail vs. Park City vs. Tahoe and you need to reason about hub accessibility and which mountains are actually in play.
The final selector supports three user types:
- Quick compare: “Hubs only” → one click, search DEN/SLC/RNO destinations
- Explore a region: select Denver hub, see all accessible resorts with their actual airports
- Specific search: expand Salt Lake, deselect Brighton, keep Snowbird → granular control without losing the hub structure
One more filter that fell out of this: minimum vertical. I don’t care about 500-foot hills. The tool grays out destinations below a configurable threshold (default 2000’). Keeps the results focused on mountains worth flying to.
The takeaway
Your UX should match your users’ mental models, not your data model.
I had airports. I had regions. Neither matched how skiers actually plan trips. The hub field wasn’t in any API I was consuming—it came from understanding the domain well enough to know it was missing.
If your users keep getting confused by something that seems “obvious,” the abstraction is probably wrong.
---
If you enjoyed this post, you can subscribe here to get updates on new content. I don't spam and you can unsubscribe at any time.