A Travel Checklist Generator With Conditional Items and Trip Profiles
Tell the app you're going to Okinawa for 10 days in summer. It gives you a list that includes swimsuit, sunscreen, laundry bag (because 10+ days), no winter coat, no passport (domestic). Change the trip to Paris for 5 days and the list now includes passport, travel adapter, and winter coat. 107 items with conditional logic, all driven by a declarative rule language.
Every trip checklist article on the internet is the same: a generic list with "the 50 items you should never forget." But the items you need depend entirely on the trip. A business trip needs laptop + cables but not a swimsuit. A beach trip needs sunscreen but not dress shirts. Conditional logic makes one list serve every trip.
π Live demo: https://sen.ltd/portfolio/travel-checklist/
π¦ GitHub: https://github.com/sen-ltd/travel-checklist
Features:
- 107 items with conditions
- 7 categories (Documents, Clothing, Toiletries, Electronics, Health, Entertainment, Misc)
- Trip profile inputs: destination, season, duration, transport, trip type
- Saved trip profiles in localStorage
- Add custom items
- Print-friendly layout
- Japanese / English UI
- Zero dependencies, 37 tests
The item database
Each item has a name, category, priority, and conditions:
{
id: 'passport',
category: 'documents',
name: { ja: 'γγΉγγΌγ', en: 'Passport' },
conditions: { destination: ['international'] },
priority: 'critical',
},
{
id: 'swimsuit',
category: 'clothing',
name: { ja: 'ζ°΄η', en: 'Swimsuit' },
conditions: { tripType: ['beach'] },
priority: 'high',
},
{
id: 'laundry-bag',
category: 'misc',
name: { ja: 'ζ΄ζΏ―θ’', en: 'Laundry bag' },
conditions: { minDays: 7 },
priority: 'low',
},
Conditions can be:
-
Array match:
destination: ['international']β match if profile is in list -
Numeric threshold:
minDays: 7β match if duration β₯ 7 -
Logic:
conditionLogic: 'any'β OR instead of the default AND
The matcher
export function matchesConditions(item, profile) {
const cond = item.conditions || {};
const keys = Object.keys(cond);
if (keys.length === 0) return true; // always needed
const checks = keys.filter(k => k !== 'conditionLogic').map(key => {
if (key === 'minDays') return profile.days >= cond.minDays;
if (key === 'maxDays') return profile.days <= cond.maxDays;
if (Array.isArray(cond[key])) {
return cond[key].includes(profile[key]);
}
return false;
});
return cond.conditionLogic === 'any'
? checks.some(Boolean)
: checks.every(Boolean);
}
Default is AND logic: all conditions must match. conditionLogic: 'any' switches to OR β useful for items like sunscreen that apply to summer OR beach trips regardless of season.
Sunscreen as an OR case
{
id: 'sunscreen',
conditions: {
season: ['summer'],
tripType: ['beach', 'adventure'],
conditionLogic: 'any',
},
}
Match if: summer season, OR beach trip, OR adventure trip. All three mean "you'll be in the sun."
Without OR logic, you'd need three separate sunscreen entries. With it, one entry covers all the cases.
Why declarative conditions
The alternative is hardcoding: if (profile.destination === 'international') items.push(passport). That works for 10 items. At 100 items you have an unmaintainable mess of if-statements.
Declarative conditions keep the rules next to the data. Adding a new item means adding a row to ITEMS β no code changes. Adding a new condition type (e.g., region: ['tropical']) means one switch case in the matcher. The complexity grows linearly, not quadratically.
Series
This is entry #69 in my 100+ public portfolio series.
- π¦ Repo: https://github.com/sen-ltd/travel-checklist
- π Live: https://sen.ltd/portfolio/travel-checklist/
- π’ Company: https://sen.ltd/

Top comments (0)