// Travel app UI
Dark Travel Booking App UI Design in Expo
This travel booking app UI uses a single neon lime accent on charcoal to make every action obvious. A coastal onboarding photo leads into destination cards and a package page.
- Screens
- 3
- Built with
- Expo SDK 58
- Fonts
- Urbanist
- License
- Free (MIT)

Adventure Travel Booking app screens

Onboarding
A dramatic coastline with a large headline, a short line of copy and a lime Get Started pill.

Destinations
Profile and notifications, search, filter chips and tall destination cards under a floating tab bar.

Trip package
A Bali photo with a price, a gallery row, duration, distance and reviews, plus a description and Book Now.
Copy the Adventure Travel Booking kit
Use the artifact as a design brief, the skill to generate the UI with an AI agent, or the code to run it right away.
Design artifact
The full design: tokens, screens and source
# Adventure Travel Booking: design artifact
This travel booking app UI uses a single neon lime accent on charcoal to make every action obvious. A coastal onboarding photo leads into destination cards and a package page.
- Type: Travel mobile app UI
- Screens: 3
- Artboard: 390 × 844
- Target: Expo SDK 58, Expo Router, TypeScript, expo-image, expo-linear-gradient, react-native-svg
- Live page: https://developermill.com/app-designs/dark-travel-booking-app-ui-design
## Design direction
Charcoal surfaces with a neon lime accent, big aerial photography and pill-shaped controls.
- Full-screen onboarding with a pill action
- Destination filters with selected state
- Tall destination cards with a Book Now action
- Package page with photos, stats and details
## Foundations
### Colour
| Token | Hex |
| --- | --- |
| Charcoal | `#161616` |
| Graphite | `#262626` |
| Neon lime | `#D7F21C` |
| Alert red | `#FF4D4D` |
| Silver | `#BDBDBD` |
| White | `#FFFFFF` |
### Typography
- Typefaces: Urbanist
- Common sizes (px): 10, 11, 12, 13, 14, 16, 17, 20
### Shape and depth
- Common corner radii (px): 4, 14, 16, 20, 22, 24
- Shadows: `0 10px 30px rgba(0,0,0,0.45)`
## Screens
### 1. Onboarding
A dramatic coastline with a large headline, a short line of copy and a lime Get Started pill.
Preview: https://developermill.com/app-designs/dark-travel-booking-app-ui-design/welcome.webp
Copy on this screen:
- 70
- Embark on Your Next Great Adventure
- Discover exciting places, meet new people, and turn every moment into an unforgettable adventure.
- Get Started
### 2. Destinations
Profile and notifications, search, filter chips and tall destination cards under a floating tab bar.
Preview: https://developermill.com/app-designs/dark-travel-booking-app-ui-design/home.webp
Copy on this screen:
- 70
- Hi, Alex
- Welcome Back
- Popular Destination
- View All
- Indonesia
- Bali Island
- Book Now
- Raja Ampat
### 3. Trip package
A Bali photo with a price, a gallery row, duration, distance and reviews, plus a description and Book Now.
Preview: https://developermill.com/app-designs/dark-travel-booking-app-ui-design/package.webp
Copy on this screen:
- 70
- Indonesia
- Bali Island
- $999
- 15-18hrs
- Duration
- 125km
- Distance
- 4.9k
- Reviews
- About travel
- Bali is a tropical paradise that blends breathtaking natural beauty with rich cultural heritage. From emerald rice terraces and ancient temples to sun-kissed beaches and vibrant local markets, every corner of the island offers a new adventure.
- Book Now
## Full source
The original artboards as HTML. Image URLs point at the hosted demo assets.
### Onboarding (Main)
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Onboarding</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Urbanist:wght@400;500;600;700&display=swap">
<style>
body{margin:0}
a{color:#D7F21C}a:hover{color:#E9FF5C}
.ob-bg{background-image:url(https://developermill.com/app-designs/dark-travel-booking-app-ui-design/assets/e1bf02ed92.jpg);background-size:cover;background-position:center}
</style>
</helmet>
<div class="ob-bg" style="width: 390px; height: 844px; position: relative; overflow: hidden; font-family: Urbanist, system-ui, sans-serif; color: #FFFFFF; background-color: #0E1A1C">
<div style="position: absolute; inset: 0; background: linear-gradient(180deg, rgba(0,0,0,0.25) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0.15) 55%, rgba(0,0,0,0.85) 100%)"></div>
<div style="position: relative; display: flex; justify-content: space-between; align-items: center; padding: 16px 30px 0 34px; height: 38px; font-size: 16px; font-weight: 600">
<span>9:41</span>
<div style="display: flex; gap: 6px; align-items: center">
<svg width="18" height="12" viewBox="0 0 18 12" aria-hidden="true"><rect x="0" y="8" width="3" height="4" rx="1" fill="#fff"></rect><rect x="5" y="5.5" width="3" height="6.5" rx="1" fill="#fff"></rect><rect x="10" y="3" width="3" height="9" rx="1" fill="#fff"></rect><rect x="15" y="0" width="3" height="12" rx="1" fill="#fff"></rect></svg>
<svg width="16" height="12" viewBox="0 0 16 12" aria-hidden="true"><path d="M8 11.5 L5.6 9 A3.4 3.4 0 0 1 10.4 9 Z M2.8 6.2 A7.4 7.4 0 0 1 13.2 6.2 L11.8 7.6 A5.4 5.4 0 0 0 4.2 7.6 Z M0 3.4 A11.4 11.4 0 0 1 16 3.4 L14.6 4.8 A9.4 9.4 0 0 0 1.4 4.8 Z" fill="#fff"></path></svg>
<span style="background: #fff; color: #000; font-size: 11px; font-weight: 700; border-radius: 4px; padding: 1px 4px">70</span>
</div>
</div>
<div style="position: absolute; left: 24px; right: 24px; bottom: 36px; display: flex; flex-direction: column; gap: 16px">
<h1 style="margin: 0; font-size: 34px; line-height: 1.18; font-weight: 600; letter-spacing: -0.3px">Embark on Your Next Great Adventure</h1>
<p style="margin: 0; font-size: 14px; line-height: 1.55; color: rgba(255,255,255,0.82)">Discover exciting places, meet new people, and turn every moment into an unforgettable adventure.</p>
<a href="Home.dc.html" aria-label="Get started" style="margin-top: 22px; height: 62px; border-radius: 31px; background: {{accent}}; display: flex; align-items: center; padding: 0 8px; text-decoration: none; color: #111111">
<span style="width: 48px; height: 48px; border-radius: 24px; background: #141414; display: flex; align-items: center; justify-content: center">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 6l6 6-6 6"></path></svg>
</span>
<span style="flex-grow: 1; text-align: center; font-size: 17px; font-weight: 600">Get Started</span>
<span style="width: 48px; height: 48px; display: flex; align-items: center; justify-content: center">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#111" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 6l6 6-6 6"></path><path d="M12 6l6 6-6 6"></path></svg>
</span>
</a>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"accent":{"editor":"color","default":"#D7F21C","options":["#D7F21C","#7CF2C9","#FFB648"]},"$preview":{"width":390,"height":844}}'>
class Component extends DCLogic {
renderVals() {
return { accent: this.props.accent ?? '#D7F21C' };
}
}
</script>
</body>
</html>
```
### Destinations (Home)
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Urbanist:wght@400;500;600;700&display=swap">
<style>
body{margin:0}
a{color:#D7F21C}a:hover{color:#E9FF5C}
.card1{background-image:url(https://developermill.com/app-designs/dark-travel-booking-app-ui-design/assets/86739333ab.jpg);background-size:cover;background-position:center 40%}
.card2{background-image:url(https://developermill.com/app-designs/dark-travel-booking-app-ui-design/assets/9937adc1a3.jpg);background-size:cover;background-position:center 60%}
.home-scroll::-webkit-scrollbar{display:none}
</style>
</helmet>
<div style="width: 390px; height: 844px; position: relative; overflow: hidden; background: #161616; font-family: Urbanist, system-ui, sans-serif; color: #FFFFFF">
<div style="display: flex; justify-content: space-between; align-items: center; padding: 16px 30px 0 34px; height: 38px; font-size: 16px; font-weight: 600">
<span>9:41</span>
<div style="display: flex; gap: 6px; align-items: center">
<svg width="18" height="12" viewBox="0 0 18 12" aria-hidden="true"><rect x="0" y="8" width="3" height="4" rx="1" fill="#fff"></rect><rect x="5" y="5.5" width="3" height="6.5" rx="1" fill="#fff"></rect><rect x="10" y="3" width="3" height="9" rx="1" fill="#fff"></rect><rect x="15" y="0" width="3" height="12" rx="1" fill="#fff"></rect></svg>
<svg width="16" height="12" viewBox="0 0 16 12" aria-hidden="true"><path d="M8 11.5 L5.6 9 A3.4 3.4 0 0 1 10.4 9 Z M2.8 6.2 A7.4 7.4 0 0 1 13.2 6.2 L11.8 7.6 A5.4 5.4 0 0 0 4.2 7.6 Z M0 3.4 A11.4 11.4 0 0 1 16 3.4 L14.6 4.8 A9.4 9.4 0 0 0 1.4 4.8 Z" fill="#fff"></path></svg>
<span style="background: #fff; color: #000; font-size: 11px; font-weight: 700; border-radius: 4px; padding: 1px 4px">70</span>
</div>
</div>
<div class="home-scroll" style="position: absolute; top: 54px; left: 0; right: 0; bottom: 0; overflow-y: auto; padding: 14px 20px 120px; display: flex; flex-direction: column; gap: 20px; box-sizing: border-box">
<div style="display: flex; align-items: center; justify-content: space-between">
<div style="display: flex; align-items: center; gap: 12px">
<img src="https://developermill.com/app-designs/dark-travel-booking-app-ui-design/assets/94ee8837ac.jpg" alt="Alex" style="width: 50px; height: 50px; border-radius: 25px; object-fit: cover; background: {{accent}}">
<div style="display: flex; flex-direction: column; gap: 2px">
<span style="font-size: 14px; color: #C9C9C9">Hi, Alex</span>
<span style="font-size: 17px; font-weight: 600">Welcome Back</span>
</div>
</div>
<button aria-label="Notifications" style="width: 48px; height: 48px; border-radius: 24px; border: 0; background: #262626; position: relative; display: flex; align-items: center; justify-content: center; cursor: pointer">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9a6 6 0 0 1 12 0c0 6 2.5 7.5 2.5 7.5h-17S6 15 6 9"></path><path d="M10 20a2 2 0 0 0 4 0"></path></svg>
<span style="position: absolute; top: 12px; right: 13px; width: 8px; height: 8px; border-radius: 4px; background: #FF4D4D; border: 1.5px solid #262626"></span>
</button>
</div>
<div style="display: flex; gap: 12px; align-items: center">
<label style="flex-grow: 1; height: 52px; border-radius: 26px; background: #262626; display: flex; align-items: center; gap: 10px; padding: 0 18px">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#BDBDBD" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><path d="M20 20l-4-4"></path></svg>
<input type="text" placeholder="Search.." aria-label="Search destinations" style="flex-grow: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: #fff; font-family: inherit; font-size: 15px">
</label>
<button aria-label="Filters" style="width: 52px; height: 52px; border-radius: 26px; border: 0; background: #262626; display: flex; align-items: center; justify-content: center; cursor: pointer">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M4 7h10M18 7h2M4 17h4M12 17h8"></path><circle cx="16" cy="7" r="2"></circle><circle cx="10" cy="17" r="2"></circle></svg>
</button>
</div>
<div style="display: flex; gap: 10px; margin-right: -20px; overflow: hidden">
<sc-for list="{{chips}}" as="chip" hint-placeholder-count="4">
<button onClick="{{chip.pick}}" aria-label="{{chip.label}}" style="{{chip.style}}">
<sc-if value="{{chip.isIcon}}" hint-placeholder-val="{{false}}">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#111" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="7"></circle><circle cx="12" cy="12" r="2.5" fill="#111"></circle><path d="M12 2v3M12 19v3M2 12h3M19 12h3"></path></svg>
</sc-if>
<sc-if value="{{chip.isText}}" hint-placeholder-val="{{true}}">
<span>{{chip.label}}</span>
</sc-if>
</button>
</sc-for>
</div>
<div style="display: flex; justify-content: space-between; align-items: baseline">
<h2 style="margin: 0; font-size: 20px; font-weight: 600">Popular Destination</h2>
<a href="#" style="font-size: 14px; font-weight: 500; text-decoration: none; color: {{accent}}">View All</a>
</div>
<div class="card1" style="height: 300px; border-radius: 28px; position: relative; overflow: hidden; flex-shrink: 0">
<div style="position: absolute; inset: 0; background: linear-gradient(180deg, rgba(0,0,0,0) 50%, rgba(0,0,0,0.6) 100%)"></div>
<button onClick="{{toggleFav1}}" aria-label="Save Bali Island" style="position: absolute; top: 14px; right: 14px; width: 40px; height: 40px; border-radius: 20px; border: 0; background: rgba(255,255,255,0.28); display: flex; align-items: center; justify-content: center; cursor: pointer">
<svg width="20" height="20" viewBox="0 0 24 24" fill="{{fav1Fill}}" stroke="#fff" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.5-9.2-9C1.3 7.7 3.5 4.5 6.8 4.5c2.1 0 3.6 1.2 5.2 3 1.6-1.8 3.1-3 5.2-3 3.3 0 5.5 3.2 4 6.5C19 15.5 12 20 12 20z"></path></svg>
</button>
<div style="position: absolute; left: 18px; right: 14px; bottom: 16px; display: flex; justify-content: space-between; align-items: flex-end">
<div style="display: flex; flex-direction: column; gap: 2px">
<span style="font-size: 12px; color: rgba(255,255,255,0.85)">Indonesia</span>
<span style="font-size: 20px; font-weight: 600">Bali Island</span>
</div>
<a href="Detail.dc.html" style="height: 40px; padding: 0 20px; border-radius: 20px; background: {{accent}}; color: #111; font-size: 14px; font-weight: 600; display: flex; align-items: center; text-decoration: none">Book Now</a>
</div>
</div>
<div class="card2" style="height: 300px; border-radius: 28px; position: relative; overflow: hidden; flex-shrink: 0">
<div style="position: absolute; inset: 0; background: linear-gradient(180deg, rgba(0,0,0,0) 50%, rgba(0,0,0,0.6) 100%)"></div>
<button onClick="{{toggleFav2}}" aria-label="Save destination" style="position: absolute; top: 14px; right: 14px; width: 40px; height: 40px; border-radius: 20px; border: 0; background: rgba(255,255,255,0.28); display: flex; align-items: center; justify-content: center; cursor: pointer">
<svg width="20" height="20" viewBox="0 0 24 24" fill="{{fav2Fill}}" stroke="#fff" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.5-9.2-9C1.3 7.7 3.5 4.5 6.8 4.5c2.1 0 3.6 1.2 5.2 3 1.6-1.8 3.1-3 5.2-3 3.3 0 5.5 3.2 4 6.5C19 15.5 12 20 12 20z"></path></svg>
</button>
<div style="position: absolute; left: 18px; right: 14px; bottom: 16px; display: flex; justify-content: space-between; align-items: flex-end">
<div style="display: flex; flex-direction: column; gap: 2px">
<span style="font-size: 12px; color: rgba(255,255,255,0.85)">Indonesia</span>
<span style="font-size: 20px; font-weight: 600">Raja Ampat</span>
</div>
<a href="Detail.dc.html" style="height: 40px; padding: 0 20px; border-radius: 20px; background: {{accent}}; color: #111; font-size: 14px; font-weight: 600; display: flex; align-items: center; text-decoration: none">Book Now</a>
</div>
</div>
</div>
<nav aria-label="Main" style="position: absolute; left: 50%; bottom: 26px; transform: translateX(-50%); height: 64px; padding: 0 8px; border-radius: 32px; background: rgba(48,48,48,0.92); display: flex; align-items: center; gap: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.45)">
<sc-for list="{{tabs}}" as="tab" hint-placeholder-count="4">
<button onClick="{{tab.pick}}" aria-label="{{tab.label}}" style="{{tab.style}}">
<sc-if value="{{tab.home}}" hint-placeholder-val="{{false}}"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="{{tab.color}}" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M4 10.5L12 4l8 6.5V20h-5v-5h-6v5H4z"></path></svg></sc-if>
<sc-if value="{{tab.tag}}" hint-placeholder-val="{{false}}"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="{{tab.color}}" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M3 12V4h8l10 10-8 8z"></path><circle cx="7.5" cy="8" r="1.5" fill="{{tab.color}}"></circle></svg></sc-if>
<sc-if value="{{tab.cal}}" hint-placeholder-val="{{false}}"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="{{tab.color}}" stroke-width="2" stroke-linecap="round" aria-hidden="true"><rect x="4" y="5" width="16" height="15" rx="2"></rect><path d="M8 3v4M16 3v4M4 10h16M8 14h2M14 14h2M8 17h2"></path></svg></sc-if>
<sc-if value="{{tab.user}}" hint-placeholder-val="{{false}}"><svg width="22" height="22" viewBox="0 0 24 24" fill="{{tab.color}}" aria-hidden="true"><circle cx="12" cy="8" r="4"></circle><path d="M4 21c0-4.4 3.6-7 8-7s8 2.6 8 7z"></path></svg></sc-if>
</button>
</sc-for>
</nav>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"accent":{"editor":"color","default":"#D7F21C","options":["#D7F21C","#7CF2C9","#FFB648"]},"$preview":{"width":390,"height":844}}'>
class Component extends DCLogic {
constructor(p) { super(p); this.state = { chip: 0, tab: 0, fav1: false, fav2: false }; }
renderVals() {
const accent = this.props.accent ?? '#D7F21C';
const labels = ['Nearby', 'Ocean', 'Mountain', 'Forest', 'Desert'];
const chips = labels.map((label, i) => {
const on = this.state.chip === i;
const base = 'height: 48px; border-radius: 24px; border: 0; flex-shrink: 0; font-family: inherit; font-size: 14px; font-weight: 500; cursor: pointer; display: flex; align-items: center; justify-content: center; ';
const size = i === 0 ? 'width: 76px; ' : 'padding: 0 26px; ';
const color = on ? `background: ${accent}; color: #111111;` : 'background: #262626; color: #FFFFFF;';
return { label, isIcon: i === 0, isText: i !== 0, style: base + size + color, pick: () => this.setState({ chip: i }) };
});
const tabNames = ['Home', 'Offers', 'Bookings', 'Profile'];
const tabs = tabNames.map((label, i) => {
const on = this.state.tab === i;
return {
label, home: i === 0, tag: i === 1, cal: i === 2, user: i === 3,
color: on ? '#111111' : '#FFFFFF',
style: `width: 48px; height: 48px; border-radius: 24px; border: 0; cursor: pointer; display: flex; align-items: center; justify-content: center; background: ${on ? accent : 'transparent'};`,
pick: () => this.setState({ tab: i })
};
});
return {
accent, chips, tabs,
fav1Fill: this.state.fav1 ? '#FF4D4D' : 'none',
fav2Fill: this.state.fav2 ? '#FF4D4D' : 'none',
toggleFav1: () => this.setState({ fav1: !this.state.fav1 }),
toggleFav2: () => this.setState({ fav2: !this.state.fav2 })
};
}
}
</script>
</body>
</html>
```
### Trip package (Detail)
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Bali Island details</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Urbanist:wght@400;500;600;700&display=swap">
<style>
body{margin:0}
a{color:#D7F21C}a:hover{color:#E9FF5C}
.hero{background-image:url(https://developermill.com/app-designs/dark-travel-booking-app-ui-design/assets/86739333ab.jpg);background-size:cover;background-position:center 30%}
</style>
</helmet>
<div style="width: 390px; height: 844px; position: relative; overflow: hidden; background: #161616; font-family: Urbanist, system-ui, sans-serif; color: #FFFFFF">
<div class="hero" style="position: absolute; top: 0; left: 0; right: 0; height: 450px">
<div style="position: absolute; inset: 0; background: linear-gradient(180deg, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0) 25%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.55) 100%)"></div>
</div>
<div style="position: relative; display: flex; justify-content: space-between; align-items: center; padding: 16px 30px 0 34px; height: 38px; font-size: 16px; font-weight: 600">
<span>9:41</span>
<div style="display: flex; gap: 6px; align-items: center">
<svg width="18" height="12" viewBox="0 0 18 12" aria-hidden="true"><rect x="0" y="8" width="3" height="4" rx="1" fill="#fff"></rect><rect x="5" y="5.5" width="3" height="6.5" rx="1" fill="#fff"></rect><rect x="10" y="3" width="3" height="9" rx="1" fill="#fff"></rect><rect x="15" y="0" width="3" height="12" rx="1" fill="#fff"></rect></svg>
<svg width="16" height="12" viewBox="0 0 16 12" aria-hidden="true"><path d="M8 11.5 L5.6 9 A3.4 3.4 0 0 1 10.4 9 Z M2.8 6.2 A7.4 7.4 0 0 1 13.2 6.2 L11.8 7.6 A5.4 5.4 0 0 0 4.2 7.6 Z M0 3.4 A11.4 11.4 0 0 1 16 3.4 L14.6 4.8 A9.4 9.4 0 0 0 1.4 4.8 Z" fill="#fff"></path></svg>
<span style="background: #fff; color: #000; font-size: 11px; font-weight: 700; border-radius: 4px; padding: 1px 4px">70</span>
</div>
</div>
<div style="position: absolute; top: 62px; left: 20px; right: 20px; display: flex; justify-content: space-between">
<a href="Home.dc.html" aria-label="Back" style="width: 44px; height: 44px; border-radius: 22px; background: rgba(255,255,255,0.3); display: flex; align-items: center; justify-content: center">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 12H5M11 6l-6 6 6 6"></path></svg>
</a>
<button onClick="{{toggleFav}}" aria-label="Save Bali Island" style="width: 44px; height: 44px; border-radius: 22px; border: 0; background: rgba(255,255,255,0.3); display: flex; align-items: center; justify-content: center; cursor: pointer">
<svg width="20" height="20" viewBox="0 0 24 24" fill="{{favFill}}" stroke="{{favStroke}}" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.5-9.2-9C1.3 7.7 3.5 4.5 6.8 4.5c2.1 0 3.6 1.2 5.2 3 1.6-1.8 3.1-3 5.2-3 3.3 0 5.5 3.2 4 6.5C19 15.5 12 20 12 20z"></path></svg>
</button>
</div>
<div style="position: absolute; top: 352px; left: 20px; right: 20px; display: flex; justify-content: space-between; align-items: flex-end">
<div style="display: flex; flex-direction: column; gap: 2px">
<span style="font-size: 13px; color: rgba(255,255,255,0.88)">Indonesia</span>
<span style="font-size: 22px; font-weight: 600">Bali Island</span>
</div>
<span style="font-size: 32px; font-weight: 700; color: {{accent}}">$999</span>
</div>
<div style="position: absolute; top: 420px; left: 0; right: 0; bottom: 0; background: #161616; border-radius: 28px 28px 0 0; padding: 18px 20px 0; display: flex; flex-direction: column; gap: 18px">
<div style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px">
<img src="https://developermill.com/app-designs/dark-travel-booking-app-ui-design/assets/89dc6d3905.jpg" alt="Limestone rocks over a coral reef" style="width: 100%; height: 76px; border-radius: 14px; object-fit: cover">
<img src="https://developermill.com/app-designs/dark-travel-booking-app-ui-design/assets/2d7ae2dab1.jpg" alt="White sand beach and turquoise bay" style="width: 100%; height: 76px; border-radius: 14px; object-fit: cover">
<img src="https://developermill.com/app-designs/dark-travel-booking-app-ui-design/assets/b5d23dbdd7.jpg" alt="Small island ringed by reef" style="width: 100%; height: 76px; border-radius: 14px; object-fit: cover">
</div>
<div style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px">
<div style="height: 44px; border-radius: 22px; background: #262626; display: flex; align-items: center; gap: 8px; padding: 0 6px">
<span style="width: 32px; height: 32px; border-radius: 16px; background: {{accent}}; display: flex; align-items: center; justify-content: center; flex-shrink: 0"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#111" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="8"></circle><path d="M12 8v4l3 2"></path></svg></span>
<div style="display: flex; flex-direction: column; line-height: 1.15"><span style="font-size: 12px; font-weight: 600">15-18hrs</span><span style="font-size: 10px; color: #B5B5B5">Duration</span></div>
</div>
<div style="height: 44px; border-radius: 22px; background: #262626; display: flex; align-items: center; gap: 8px; padding: 0 6px">
<span style="width: 32px; height: 32px; border-radius: 16px; background: {{accent}}; display: flex; align-items: center; justify-content: center; flex-shrink: 0"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#111" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="6"></circle><circle cx="12" cy="12" r="2" fill="#111"></circle><path d="M12 2v3M12 19v3M2 12h3M19 12h3"></path></svg></span>
<div style="display: flex; flex-direction: column; line-height: 1.15"><span style="font-size: 12px; font-weight: 600">125km</span><span style="font-size: 10px; color: #B5B5B5">Distance</span></div>
</div>
<div style="height: 44px; border-radius: 22px; background: #262626; display: flex; align-items: center; gap: 8px; padding: 0 6px">
<span style="width: 32px; height: 32px; border-radius: 16px; background: {{accent}}; display: flex; align-items: center; justify-content: center; flex-shrink: 0"><svg width="16" height="16" viewBox="0 0 24 24" fill="#111" aria-hidden="true"><path d="M12 3l2.6 5.6 6 .7-4.5 4.1 1.2 6L12 16.4 6.7 19.4l1.2-6L3.4 9.3l6-.7z"></path></svg></span>
<div style="display: flex; flex-direction: column; line-height: 1.15"><span style="font-size: 12px; font-weight: 600">4.9k</span><span style="font-size: 10px; color: #B5B5B5">Reviews</span></div>
</div>
</div>
<div style="display: flex; flex-direction: column; gap: 10px">
<h2 style="margin: 0; font-size: 22px; font-weight: 600">About travel</h2>
<p style="margin: 0; font-size: 13px; line-height: 1.6; color: #CFCFCF">Bali is a tropical paradise that blends breathtaking natural beauty with rich cultural heritage. From emerald rice terraces and ancient temples to sun-kissed beaches and vibrant local markets, every corner of the island offers a new adventure.</p>
</div>
</div>
<div style="position: absolute; left: 20px; right: 20px; bottom: 30px; display: flex; gap: 12px; align-items: center">
<button aria-label="Share" style="width: 58px; height: 58px; border-radius: 29px; border: 0; background: #FFFFFF; display: flex; align-items: center; justify-content: center; flex-shrink: 0; cursor: pointer">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#111" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="18" cy="5" r="2.5"></circle><circle cx="6" cy="12" r="2.5"></circle><circle cx="18" cy="19" r="2.5"></circle><path d="M8.2 10.8l7.6-4.4M8.2 13.2l7.6 4.4"></path></svg>
</button>
<button style="flex-grow: 1; height: 58px; border-radius: 29px; border: 0; background: {{accent}}; color: #111111; font-family: inherit; font-size: 17px; font-weight: 600; cursor: pointer">Book Now</button>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"accent":{"editor":"color","default":"#D7F21C","options":["#D7F21C","#7CF2C9","#FFB648"]},"$preview":{"width":390,"height":844}}'>
class Component extends DCLogic {
constructor(p) { super(p); this.state = { fav: true }; }
renderVals() {
return {
accent: this.props.accent ?? '#D7F21C',
favFill: this.state.fav ? '#FF4D4D' : 'none',
favStroke: this.state.fav ? '#FF4D4D' : '#FFFFFF',
toggleFav: () => this.setState({ fav: !this.state.fav })
};
}
}
</script>
</body>
</html>
```SKILL.md
Save as .claude/skills/dark-travel-booking-app-ui/SKILL.md
--- name: dark-travel-booking-app-ui description: Build the Adventure Travel Booking mobile app UI in Expo React Native. Use when asked to create a travel booking app UI or a new screen in this visual style. --- # Adventure Travel Booking app UI Build this travel app UI in an Expo Router project with TypeScript. Reference: https://developermill.com/app-designs/dark-travel-booking-app-ui-design ## Visual direction Charcoal surfaces with a neon lime accent, big aerial photography and pill-shaped controls. Keep these signature details: - Full-screen onboarding with a pill action - Destination filters with selected state - Tall destination cards with a Book Now action - Package page with photos, stats and details ## Set up ```bash npx expo install expo-image expo-linear-gradient react-native-svg @expo-google-fonts/urbanist expo-font ``` ## Tokens | Name | Hex | | --- | --- | | Charcoal | `#161616` | | Graphite | `#262626` | | Neon lime | `#D7F21C` | | Alert red | `#FF4D4D` | | Silver | `#BDBDBD` | | White | `#FFFFFF` | - Typefaces: Urbanist - Text sizes (px): 10, 11, 12, 13, 14, 16, 17, 20 - Corner radii (px): 4, 14, 16, 20, 22, 24 Define these once and reuse them. Do not add new colours. ## Screens 1. **Onboarding.** A dramatic coastline with a large headline, a short line of copy and a lime Get Started pill. 2. **Destinations.** Profile and notifications, search, filter chips and tall destination cards under a floating tab bar. 3. **Trip package.** A Bali photo with a price, a gallery row, duration, distance and reviews, plus a description and Book Now. ## Build rules - Draw each screen on a 390 × 844 artboard and scale it to the device width. - Load each custom font weight by family name with expo-font. Do not set fontWeight with custom fonts. - Draw icons, charts and illustrations with react-native-svg. - Use Pressable with an accessibilityLabel for controls, with at least a 44px target. - Navigate with router.navigate and keep sample data as constants at the top of the file. - Use expo-image with contentFit for photos. ## Done when - All routes are linked and reachable. - Colours, type and radii match the tokens above. - `npx tsc --noEmit` and `npx expo lint` pass. - The layout holds up on small and large phones.
Set up the project
npx create-expo-app@latest dark-travel-booking-app --template default cd dark-travel-booking-app rm -rf src npx expo install expo-image expo-linear-gradient react-native-svg @expo-google-fonts/urbanist expo-font
src/app/_layout.tsx
import {
Urbanist_400Regular,
Urbanist_500Medium,
Urbanist_600SemiBold,
Urbanist_700Bold,
} from '@expo-google-fonts/urbanist';
import { useFonts } from 'expo-font';
import { Stack } from 'expo-router';
export default function RootLayout() {
const [fontsLoaded] = useFonts({
Urbanist_400Regular,
Urbanist_500Medium,
Urbanist_600SemiBold,
Urbanist_700Bold,
});
if (!fontsLoaded) return null;
return <Stack screenOptions={{ headerShown: false }} />;
}src/app/index.tsx
import { Image } from 'expo-image';
import { LinearGradient } from 'expo-linear-gradient';
import { router } from 'expo-router';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import Svg, { Path } from 'react-native-svg';
import { Screen } from '@/components/screen';
import { asset } from '@/lib/assets';
const accent = '#D7F21C';
export default function MainScreen() {
return (
<Screen background="#0E1A1C" statusBar="light">
<View style={{ flex: 1 }}>
<Image
source={{ uri: asset('e1bf02ed92.jpg') }}
contentFit="cover"
contentPosition={{ left: '50%', top: '50%' }}
style={StyleSheet.absoluteFill}
/>
<View style={{ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 }}>
<LinearGradient
colors={['rgba(0,0,0,0.25)', 'rgba(0,0,0,0)', 'rgba(0,0,0,0.15)', 'rgba(0,0,0,0.85)']}
locations={[0, 0.3, 0.55, 1]}
start={{ x: 0.5, y: 0 }}
end={{ x: 0.5, y: 1 }}
style={StyleSheet.absoluteFill}
/>
</View>
<View
style={{
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: 16,
paddingBottom: 0,
paddingLeft: 34,
paddingRight: 30,
height: 54,
flexDirection: 'row',
}}
/>
<View style={{ position: 'absolute', left: 24, right: 24, bottom: 36, gap: 16 }}>
<Text
style={{
margin: 0,
fontFamily: 'Urbanist_600SemiBold',
fontSize: 34,
color: '#FFFFFF',
letterSpacing: -0.3,
lineHeight: 40.12,
}}>
Embark on Your Next Great Adventure
</Text>
<Text
style={{
margin: 0,
fontFamily: 'Urbanist_400Regular',
fontSize: 14,
color: 'rgba(255,255,255,0.82)',
lineHeight: 21.7,
}}>
Discover exciting places, meet new people, and turn every moment into an unforgettable
adventure.
</Text>
<Pressable
accessibilityRole="link"
accessibilityLabel="Get started"
onPress={() => router.navigate('/home')}
style={{
marginTop: 22,
height: 62,
borderRadius: 31,
backgroundColor: accent,
alignItems: 'center',
paddingVertical: 0,
paddingHorizontal: 8,
flexDirection: 'row',
}}>
<View
style={{
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: '#141414',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="#fff"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M9 6l6 6-6 6" />
</Svg>
</View>
<Text
style={{
flexGrow: 1,
flexShrink: 1,
fontFamily: 'Urbanist_600SemiBold',
fontSize: 17,
color: '#111111',
textAlign: 'center',
}}>
Get Started
</Text>
<View
style={{
width: 48,
height: 48,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#111"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M6 6l6 6-6 6" />
<Path d="M12 6l6 6-6 6" />
</Svg>
</View>
</Pressable>
</View>
</View>
</Screen>
);
}src/app/detail.tsx
import { Image } from 'expo-image';
import { LinearGradient } from 'expo-linear-gradient';
import { router } from 'expo-router';
import { useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import Svg, { Circle, Path } from 'react-native-svg';
import { Screen } from '@/components/screen';
import { asset } from '@/lib/assets';
// Screen state, and the values the layout below reads from it.
function useScreen() {
const [state, setScreenState] = useState<Record<string, any>>({ fav: true });
const setState = (patch: Record<string, any>) =>
setScreenState((current) => ({ ...current, ...patch }));
return {
accent: '#D7F21C',
favFill: state.fav ? '#FF4D4D' : 'none',
favStroke: state.fav ? '#FF4D4D' : '#FFFFFF',
toggleFav: () => setState({ fav: !state.fav }),
};
}
export default function DetailScreen() {
const { accent, favFill, favStroke, toggleFav } = useScreen();
return (
<Screen background="#161616" statusBar="light">
<View style={{ flex: 1 }}>
<View style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 450 }}>
<Image
source={{ uri: asset('86739333ab.jpg') }}
contentFit="cover"
contentPosition={{ left: '50%', top: '30%' }}
style={StyleSheet.absoluteFill}
/>
<View style={{ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 }}>
<LinearGradient
colors={['rgba(0,0,0,0.2)', 'rgba(0,0,0,0)', 'rgba(0,0,0,0)', 'rgba(0,0,0,0.55)']}
locations={[0, 0.25, 0.6, 1]}
start={{ x: 0.5, y: 0 }}
end={{ x: 0.5, y: 1 }}
style={StyleSheet.absoluteFill}
/>
</View>
</View>
<View
style={{
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: 16,
paddingBottom: 0,
paddingLeft: 34,
paddingRight: 30,
height: 54,
flexDirection: 'row',
}}
/>
<View
style={{
position: 'absolute',
top: 62,
left: 20,
right: 20,
justifyContent: 'space-between',
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="link"
accessibilityLabel="Back"
onPress={() => router.navigate('/home')}
style={{
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: 'rgba(255,255,255,0.3)',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#fff"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M19 12H5M11 6l-6 6 6 6" />
</Svg>
</Pressable>
<Pressable
accessibilityRole="button"
accessibilityLabel="Save Bali Island"
onPress={toggleFav}
style={{
width: 44,
height: 44,
borderRadius: 22,
borderWidth: 0,
backgroundColor: 'rgba(255,255,255,0.3)',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill={favFill}
stroke={favStroke}
strokeWidth="2"
strokeLinejoin="round">
<Path d="M12 20s-7-4.5-9.2-9C1.3 7.7 3.5 4.5 6.8 4.5c2.1 0 3.6 1.2 5.2 3 1.6-1.8 3.1-3 5.2-3 3.3 0 5.5 3.2 4 6.5C19 15.5 12 20 12 20z" />
</Svg>
</Pressable>
</View>
<View
style={{
position: 'absolute',
top: 352,
left: 20,
right: 20,
justifyContent: 'space-between',
alignItems: 'flex-end',
flexDirection: 'row',
}}>
<View style={{ gap: 2, flexShrink: 1 }}>
<Text
style={{
fontFamily: 'Urbanist_400Regular',
fontSize: 13,
color: 'rgba(255,255,255,0.88)',
}}>
Indonesia
</Text>
<Text style={{ fontFamily: 'Urbanist_600SemiBold', fontSize: 22, color: '#FFFFFF' }}>
Bali Island
</Text>
</View>
<Text
style={{ flexShrink: 1, fontFamily: 'Urbanist_700Bold', fontSize: 32, color: accent }}>
$999
</Text>
</View>
<View
style={{
position: 'absolute',
top: 420,
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#161616',
borderTopLeftRadius: 28,
borderTopRightRadius: 28,
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
paddingTop: 18,
paddingBottom: 0,
paddingHorizontal: 20,
gap: 18,
}}>
<View style={{ flexDirection: 'row', gap: 10 }}>
<Image
source={{ uri: asset('89dc6d3905.jpg') }}
contentFit="cover"
accessibilityLabel="Limestone rocks over a coral reef"
style={{ width: '100%', height: 76, borderRadius: 14, flex: 1 }}
/>
<Image
source={{ uri: asset('2d7ae2dab1.jpg') }}
contentFit="cover"
accessibilityLabel="White sand beach and turquoise bay"
style={{ width: '100%', height: 76, borderRadius: 14, flex: 1 }}
/>
<Image
source={{ uri: asset('b5d23dbdd7.jpg') }}
contentFit="cover"
accessibilityLabel="Small island ringed by reef"
style={{ width: '100%', height: 76, borderRadius: 14, flex: 1 }}
/>
</View>
<View style={{ flexDirection: 'row', gap: 8 }}>
<View
style={{
height: 44,
borderRadius: 22,
backgroundColor: '#262626',
alignItems: 'center',
gap: 8,
paddingVertical: 0,
paddingHorizontal: 6,
flex: 1,
flexDirection: 'row',
}}>
<View
style={{
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
flexDirection: 'row',
}}>
<Svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="#111"
strokeWidth="2.2"
strokeLinecap="round">
<Circle cx="12" cy="12" r="8" />
<Path d="M12 8v4l3 2" />
</Svg>
</View>
<View style={{ flexShrink: 1 }}>
<Text
style={{
fontFamily: 'Urbanist_600SemiBold',
fontSize: 12,
color: '#FFFFFF',
lineHeight: 13.8,
}}>
15-18hrs
</Text>
<Text
style={{
fontFamily: 'Urbanist_400Regular',
fontSize: 10,
color: '#B5B5B5',
lineHeight: 11.5,
}}>
Duration
</Text>
</View>
</View>
<View
style={{
height: 44,
borderRadius: 22,
backgroundColor: '#262626',
alignItems: 'center',
gap: 8,
paddingVertical: 0,
paddingHorizontal: 6,
flex: 1,
flexDirection: 'row',
}}>
<View
style={{
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
flexDirection: 'row',
}}>
<Svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="#111"
strokeWidth="2.2"
strokeLinecap="round">
<Circle cx="12" cy="12" r="6" />
<Circle cx="12" cy="12" r="2" fill="#111" />
<Path d="M12 2v3M12 19v3M2 12h3M19 12h3" />
</Svg>
</View>
<View style={{ flexShrink: 1 }}>
<Text
style={{
fontFamily: 'Urbanist_600SemiBold',
fontSize: 12,
color: '#FFFFFF',
lineHeight: 13.8,
}}>
125km
</Text>
<Text
style={{
fontFamily: 'Urbanist_400Regular',
fontSize: 10,
color: '#B5B5B5',
lineHeight: 11.5,
}}>
Distance
</Text>
</View>
</View>
<View
style={{
height: 44,
borderRadius: 22,
backgroundColor: '#262626',
alignItems: 'center',
gap: 8,
paddingVertical: 0,
paddingHorizontal: 6,
flex: 1,
flexDirection: 'row',
}}>
<View
style={{
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
flexDirection: 'row',
}}>
<Svg width="16" height="16" viewBox="0 0 24 24" fill="#111">
<Path d="M12 3l2.6 5.6 6 .7-4.5 4.1 1.2 6L12 16.4 6.7 19.4l1.2-6L3.4 9.3l6-.7z" />
</Svg>
</View>
<View style={{ flexShrink: 1 }}>
<Text
style={{
fontFamily: 'Urbanist_600SemiBold',
fontSize: 12,
color: '#FFFFFF',
lineHeight: 13.8,
}}>
4.9k
</Text>
<Text
style={{
fontFamily: 'Urbanist_400Regular',
fontSize: 10,
color: '#B5B5B5',
lineHeight: 11.5,
}}>
Reviews
</Text>
</View>
</View>
</View>
<View style={{ gap: 10 }}>
<Text
style={{
margin: 0,
fontFamily: 'Urbanist_600SemiBold',
fontSize: 22,
color: '#FFFFFF',
}}>
About travel
</Text>
<Text
style={{
margin: 0,
fontFamily: 'Urbanist_400Regular',
fontSize: 13,
color: '#CFCFCF',
lineHeight: 20.8,
}}>
Bali is a tropical paradise that blends breathtaking natural beauty with rich cultural
heritage. From emerald rice terraces and ancient temples to sun-kissed beaches and
vibrant local markets, every corner of the island offers a new adventure.
</Text>
</View>
</View>
<View
style={{
position: 'absolute',
left: 20,
right: 20,
bottom: 30,
gap: 12,
alignItems: 'center',
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="button"
accessibilityLabel="Share"
style={{
width: 58,
height: 58,
borderRadius: 29,
borderWidth: 0,
backgroundColor: '#FFFFFF',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#111"
strokeWidth="2"
strokeLinecap="round">
<Circle cx="18" cy="5" r="2.5" />
<Circle cx="6" cy="12" r="2.5" />
<Circle cx="18" cy="19" r="2.5" />
<Path d="M8.2 10.8l7.6-4.4M8.2 13.2l7.6 4.4" />
</Svg>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
flexGrow: 1,
height: 58,
borderRadius: 29,
borderWidth: 0,
backgroundColor: accent,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Urbanist_600SemiBold',
fontSize: 17,
color: '#111111',
textAlign: 'center',
}}>
Book Now
</Text>
</Pressable>
</View>
</View>
</Screen>
);
}src/app/home.tsx
import { Image } from 'expo-image';
import { LinearGradient } from 'expo-linear-gradient';
import { router } from 'expo-router';
import { useState } from 'react';
import { Pressable, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native';
import Svg, { Circle, Path, Rect } from 'react-native-svg';
import { Screen } from '@/components/screen';
import { asset } from '@/lib/assets';
import { css, cssText } from '@/lib/css';
// Screen state, and the values the layout below reads from it.
function useScreen() {
const [state, setScreenState] = useState<Record<string, any>>({
chip: 0,
tab: 0,
fav1: false,
fav2: false,
});
const setState = (patch: Record<string, any>) =>
setScreenState((current) => ({ ...current, ...patch }));
const accent = '#D7F21C';
const labels = ['Nearby', 'Ocean', 'Mountain', 'Forest', 'Desert'];
const chips = labels.map((label, i) => {
const on = state.chip === i;
const base =
'height: 48px; border-radius: 24px; border: 0; flex-shrink: 0; font-family: inherit; font-size: 14px; font-weight: 500; cursor: pointer; display: flex; align-items: center; justify-content: center; ';
const size = i === 0 ? 'width: 76px; ' : 'padding: 0 26px; ';
const color = on
? `background: ${accent}; color: #111111;`
: 'background: #262626; color: #FFFFFF;';
return {
label,
isIcon: i === 0,
isText: i !== 0,
style: base + size + color,
pick: () => setState({ chip: i }),
};
});
const tabNames = ['Home', 'Offers', 'Bookings', 'Profile'];
const tabs = tabNames.map((label, i) => {
const on = state.tab === i;
return {
label,
home: i === 0,
tag: i === 1,
cal: i === 2,
user: i === 3,
color: on ? '#111111' : '#FFFFFF',
style: `width: 48px; height: 48px; border-radius: 24px; border: 0; cursor: pointer; display: flex; align-items: center; justify-content: center; background: ${on ? accent : 'transparent'};`,
pick: () => setState({ tab: i }),
};
});
return {
accent,
chips,
tabs,
fav1Fill: state.fav1 ? '#FF4D4D' : 'none',
fav2Fill: state.fav2 ? '#FF4D4D' : 'none',
toggleFav1: () => setState({ fav1: !state.fav1 }),
toggleFav2: () => setState({ fav2: !state.fav2 }),
};
}
export default function HomeScreen() {
const { accent, chips, tabs, fav1Fill, fav2Fill, toggleFav1, toggleFav2 } = useScreen();
return (
<Screen background="#161616" statusBar="light">
<View style={{ flex: 1 }}>
<View
style={{
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: 16,
paddingBottom: 0,
paddingLeft: 34,
paddingRight: 30,
height: 54,
flexDirection: 'row',
}}
/>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{ gap: 20, paddingHorizontal: 20, paddingBottom: 120 }}
style={{ position: 'absolute', top: 54, left: 0, right: 0, bottom: 0, paddingTop: 14 }}>
<View
style={{ alignItems: 'center', justifyContent: 'space-between', flexDirection: 'row' }}>
<View style={{ alignItems: 'center', gap: 12, flexShrink: 1, flexDirection: 'row' }}>
<Image
source={{ uri: asset('94ee8837ac.jpg') }}
contentFit="cover"
accessibilityLabel="Alex"
style={{ width: 50, height: 50, borderRadius: 25, backgroundColor: accent }}
/>
<View style={{ gap: 2, flexShrink: 1 }}>
<Text style={{ fontFamily: 'Urbanist_400Regular', fontSize: 14, color: '#C9C9C9' }}>
Hi, Alex
</Text>
<Text
style={{ fontFamily: 'Urbanist_600SemiBold', fontSize: 17, color: '#FFFFFF' }}>
Welcome Back
</Text>
</View>
</View>
<Pressable
accessibilityRole="button"
accessibilityLabel="Notifications"
style={{
width: 48,
height: 48,
borderRadius: 24,
borderWidth: 0,
backgroundColor: '#262626',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#fff"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M6 9a6 6 0 0 1 12 0c0 6 2.5 7.5 2.5 7.5h-17S6 15 6 9" />
<Path d="M10 20a2 2 0 0 0 4 0" />
</Svg>
<View
style={{
position: 'absolute',
top: 12,
right: 13,
width: 11,
height: 11,
borderRadius: 4,
backgroundColor: '#FF4D4D',
borderWidth: 1.5,
borderColor: '#262626',
}}
/>
</Pressable>
</View>
<View style={{ gap: 12, alignItems: 'center', flexDirection: 'row' }}>
<View
style={{
flexGrow: 1,
height: 52,
borderRadius: 26,
backgroundColor: '#262626',
alignItems: 'center',
gap: 10,
paddingVertical: 0,
paddingHorizontal: 18,
flexShrink: 1,
flexDirection: 'row',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#BDBDBD"
strokeWidth="2"
strokeLinecap="round">
<Circle cx="11" cy="11" r="7" />
<Path d="M20 20l-4-4" />
</Svg>
<TextInput
placeholder="Search.."
placeholderTextColor="#8A8A8A"
accessibilityLabel="Search destinations"
style={{
flexGrow: 1,
minWidth: 0,
borderWidth: 0,
flexShrink: 1,
fontFamily: 'Urbanist_400Regular',
fontSize: 15,
color: '#fff',
}}
/>
</View>
<Pressable
accessibilityRole="button"
accessibilityLabel="Filters"
style={{
width: 52,
height: 52,
borderRadius: 26,
borderWidth: 0,
backgroundColor: '#262626',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#fff"
strokeWidth="1.8"
strokeLinecap="round">
<Path d="M4 7h10M18 7h2M4 17h4M12 17h8" />
<Circle cx="16" cy="7" r="2" />
<Circle cx="10" cy="17" r="2" />
</Svg>
</Pressable>
</View>
<View style={{ gap: 10, marginRight: -20, overflow: 'hidden', flexDirection: 'row' }}>
{chips.map((chip, index) => (
<Pressable
key={index}
accessibilityRole="button"
accessibilityLabel={chip.label}
onPress={chip.pick}
style={{
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
...css(chip.style),
}}>
{chip.isIcon ? (
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#111"
strokeWidth="2"
strokeLinecap="round">
<Circle cx="12" cy="12" r="7" />
<Circle cx="12" cy="12" r="2.5" fill="#111" />
<Path d="M12 2v3M12 19v3M2 12h3M19 12h3" />
</Svg>
) : null}
{chip.isText ? (
<Text
style={[
{
fontFamily: 'Urbanist_400Regular',
fontSize: 13.33,
color: '#000000',
textAlign: 'center',
},
cssText(chip.style),
]}>
{chip.label}
</Text>
) : null}
</Pressable>
))}
</View>
<View
style={{
justifyContent: 'space-between',
alignItems: 'baseline',
flexDirection: 'row',
}}>
<Text
style={{
margin: 0,
flexShrink: 1,
fontFamily: 'Urbanist_600SemiBold',
fontSize: 20,
color: '#FFFFFF',
}}>
Popular Destination
</Text>
<Pressable accessibilityRole="link" style={{ flexShrink: 1 }}>
<Text style={{ fontFamily: 'Urbanist_500Medium', fontSize: 14, color: accent }}>
View All
</Text>
</Pressable>
</View>
<View style={{ height: 300, borderRadius: 28, overflow: 'hidden', flexShrink: 0 }}>
<Image
source={{ uri: asset('86739333ab.jpg') }}
contentFit="cover"
contentPosition={{ left: '50%', top: '40%' }}
style={StyleSheet.absoluteFill}
/>
<View style={{ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 }}>
<LinearGradient
colors={['rgba(0,0,0,0)', 'rgba(0,0,0,0.6)']}
locations={[0.5, 1]}
start={{ x: 0.5, y: 0 }}
end={{ x: 0.5, y: 1 }}
style={StyleSheet.absoluteFill}
/>
</View>
<Pressable
accessibilityRole="button"
accessibilityLabel="Save Bali Island"
onPress={toggleFav1}
style={{
position: 'absolute',
top: 14,
right: 14,
width: 40,
height: 40,
borderRadius: 20,
borderWidth: 0,
backgroundColor: 'rgba(255,255,255,0.28)',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill={fav1Fill}
stroke="#fff"
strokeWidth="2"
strokeLinejoin="round">
<Path d="M12 20s-7-4.5-9.2-9C1.3 7.7 3.5 4.5 6.8 4.5c2.1 0 3.6 1.2 5.2 3 1.6-1.8 3.1-3 5.2-3 3.3 0 5.5 3.2 4 6.5C19 15.5 12 20 12 20z" />
</Svg>
</Pressable>
<View
style={{
position: 'absolute',
left: 18,
right: 14,
bottom: 16,
justifyContent: 'space-between',
alignItems: 'flex-end',
flexDirection: 'row',
}}>
<View style={{ gap: 2, flexShrink: 1 }}>
<Text
style={{
fontFamily: 'Urbanist_400Regular',
fontSize: 12,
color: 'rgba(255,255,255,0.85)',
}}>
Indonesia
</Text>
<Text
style={{ fontFamily: 'Urbanist_600SemiBold', fontSize: 20, color: '#FFFFFF' }}>
Bali Island
</Text>
</View>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/detail')}
style={{
height: 40,
paddingVertical: 0,
paddingHorizontal: 20,
borderRadius: 20,
backgroundColor: accent,
alignItems: 'center',
flexShrink: 1,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'Urbanist_600SemiBold',
fontSize: 14,
color: '#111',
flexShrink: 1,
}}>
Book Now
</Text>
</Pressable>
</View>
</View>
<View style={{ height: 300, borderRadius: 28, overflow: 'hidden', flexShrink: 0 }}>
<Image
source={{ uri: asset('9937adc1a3.jpg') }}
contentFit="cover"
contentPosition={{ left: '50%', top: '60%' }}
style={StyleSheet.absoluteFill}
/>
<View style={{ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 }}>
<LinearGradient
colors={['rgba(0,0,0,0)', 'rgba(0,0,0,0.6)']}
locations={[0.5, 1]}
start={{ x: 0.5, y: 0 }}
end={{ x: 0.5, y: 1 }}
style={StyleSheet.absoluteFill}
/>
</View>
<Pressable
accessibilityRole="button"
accessibilityLabel="Save destination"
onPress={toggleFav2}
style={{
position: 'absolute',
top: 14,
right: 14,
width: 40,
height: 40,
borderRadius: 20,
borderWidth: 0,
backgroundColor: 'rgba(255,255,255,0.28)',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill={fav2Fill}
stroke="#fff"
strokeWidth="2"
strokeLinejoin="round">
<Path d="M12 20s-7-4.5-9.2-9C1.3 7.7 3.5 4.5 6.8 4.5c2.1 0 3.6 1.2 5.2 3 1.6-1.8 3.1-3 5.2-3 3.3 0 5.5 3.2 4 6.5C19 15.5 12 20 12 20z" />
</Svg>
</Pressable>
<View
style={{
position: 'absolute',
left: 18,
right: 14,
bottom: 16,
justifyContent: 'space-between',
alignItems: 'flex-end',
flexDirection: 'row',
}}>
<View style={{ gap: 2, flexShrink: 1 }}>
<Text
style={{
fontFamily: 'Urbanist_400Regular',
fontSize: 12,
color: 'rgba(255,255,255,0.85)',
}}>
Indonesia
</Text>
<Text
style={{ fontFamily: 'Urbanist_600SemiBold', fontSize: 20, color: '#FFFFFF' }}>
Raja Ampat
</Text>
</View>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/detail')}
style={{
height: 40,
paddingVertical: 0,
paddingHorizontal: 20,
borderRadius: 20,
backgroundColor: accent,
alignItems: 'center',
flexShrink: 1,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'Urbanist_600SemiBold',
fontSize: 14,
color: '#111',
flexShrink: 1,
}}>
Book Now
</Text>
</Pressable>
</View>
</View>
</ScrollView>
<View
style={{
position: 'absolute',
left: '50%',
bottom: 26,
transform: [{ translateX: '-50%' }],
height: 64,
paddingVertical: 0,
paddingHorizontal: 8,
borderRadius: 32,
backgroundColor: 'rgba(48,48,48,0.92)',
alignItems: 'center',
gap: 10,
boxShadow: '0 10px 30px rgba(0,0,0,0.45)',
flexDirection: 'row',
}}>
{tabs.map((tab, index) => (
<Pressable
key={index}
accessibilityRole="button"
accessibilityLabel={tab.label}
onPress={tab.pick}
style={{
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
...css(tab.style),
}}>
{tab.home ? (
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke={tab.color}
strokeWidth="2"
strokeLinejoin="round">
<Path d="M4 10.5L12 4l8 6.5V20h-5v-5h-6v5H4z" />
</Svg>
) : null}
{tab.tag ? (
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke={tab.color}
strokeWidth="2"
strokeLinejoin="round">
<Path d="M3 12V4h8l10 10-8 8z" />
<Circle cx="7.5" cy="8" r="1.5" fill={tab.color} />
</Svg>
) : null}
{tab.cal ? (
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke={tab.color}
strokeWidth="2"
strokeLinecap="round">
<Rect x="4" y="5" width="16" height="15" rx="2" />
<Path d="M8 3v4M16 3v4M4 10h16M8 14h2M14 14h2M8 17h2" />
</Svg>
) : null}
{tab.user ? (
<Svg width="22" height="22" viewBox="0 0 24 24" fill={tab.color}>
<Circle cx="12" cy="8" r="4" />
<Path d="M4 21c0-4.4 3.6-7 8-7s8 2.6 8 7z" />
</Svg>
) : null}
</Pressable>
))}
</View>
</View>
</Screen>
);
}src/components/screen.tsx
import { StatusBar } from 'expo-status-bar';
import type { ReactNode } from 'react';
import { ScrollView, useWindowDimensions, View } from 'react-native';
// Every screen is drawn on a 390 wide artboard, then scaled to the device width.
const ARTBOARD_WIDTH = 390;
type Props = {
background: string;
statusBar: 'light' | 'dark';
height?: number;
children: ReactNode;
};
export function Screen({ background, statusBar, height: artboardHeight = 844, children }: Props) {
const { width, height } = useWindowDimensions();
const scale = width / ARTBOARD_WIDTH;
return (
<ScrollView
bounces={false}
showsVerticalScrollIndicator={false}
style={{ flex: 1, backgroundColor: background }}
contentContainerStyle={{ height: Math.max(artboardHeight * scale, height) }}>
<StatusBar style={statusBar} />
<View
style={{
width: ARTBOARD_WIDTH,
height: artboardHeight,
overflow: 'hidden',
transformOrigin: 'left top',
transform: [{ scale }],
}}>
{children}
</View>
</ScrollView>
);
}src/lib/assets.ts
// Demo images are hosted by DeveloperMill. Point this at your own CDN, or swap to require().
const ASSET_BASE = 'https://developermill.com/app-designs/dark-travel-booking-app-ui-design/assets';
export const asset = (name: string) => `${ASSET_BASE}/${name}`;src/lib/css.ts
import type { TextStyle, ViewStyle } from 'react-native';
// A few styles change with state, so they are kept as short CSS strings from the design.
const FACES: Record<number, string> = {400: 'Urbanist_400Regular', 500: 'Urbanist_500Medium', 600: 'Urbanist_600SemiBold', 700: 'Urbanist_700Bold', 800: 'Urbanist_800ExtraBold'};
const px = (value: string) => (value.endsWith('%') ? (value as `${number}%`) : parseFloat(value));
function declarations(source: string) {
return source
.split(';')
.map((rule) => rule.split(':').map((part) => part.trim()))
.filter(([name, value]) => name && value);
}
export function css(source: string): ViewStyle {
const style: Record<string, unknown> = {};
for (const [name, ...rest] of declarations(source)) {
const value = rest.join(':');
if (name === 'background' || name === 'background-color') {
style.backgroundColor = value;
} else if (name === 'border') {
const [width, , ...color] = value.split(' ');
if (value === 'none' || value === '0') style.borderWidth = 0;
else {
style.borderWidth = parseFloat(width);
style.borderColor = color.join(' ');
}
} else if (name === 'display' && value === 'flex') {
style.flexDirection = style.flexDirection ?? 'row';
} else if (name === 'flex-direction') {
style.flexDirection = value;
} else if (name === 'align-items') {
style.alignItems = value;
} else if (name === 'justify-content') {
style.justifyContent = value;
} else if (name === 'box-shadow') {
style.boxShadow = value;
} else if (name === 'position') {
style.position = value === 'absolute' ? 'absolute' : 'relative';
} else if (name === 'opacity') {
style.opacity = parseFloat(value);
} else if (name === 'gap' || name === 'width' || name === 'height' || name === 'left' || name === 'top') {
style[name] = px(value);
} else if (name === 'border-radius') {
style.borderRadius = px(value);
} else if (name === 'padding') {
const [vertical, horizontal = vertical] = value.split(' ');
style.paddingVertical = px(vertical);
style.paddingHorizontal = px(horizontal);
} else if (name === 'white-space' && value === 'nowrap') {
style.flexShrink = 0;
} else if (name === 'flex-shrink') {
style.flexShrink = parseFloat(value);
} else if (name === 'flex') {
if (value === 'none') style.flexGrow = 0;
else style.flex = parseFloat(value);
}
}
return style as ViewStyle;
}
export function cssText(source: string): TextStyle {
const style: Record<string, unknown> = {};
for (const [name, value] of declarations(source)) {
if (name === 'color') style.color = value;
if (name === 'font-size') style.fontSize = parseFloat(value);
if (name === 'font-weight' && FACES[Number(value)]) style.fontFamily = FACES[Number(value)];
if (name === 'text-decoration' && value.startsWith('line-through')) {
style.textDecorationLine = 'line-through';
}
}
return style as TextStyle;
}About this design
The interactive pieces are ready: filter chips, the floating tab bar and favourites all hold state. The focused layout works well for tour operators, adventure brands and island resorts.
Charcoal surfaces with a neon lime accent, big aerial photography and pill-shaped controls.
- Full-screen onboarding with a pill action
- Destination filters with selected state
- Tall destination cards with a Book Now action
- Package page with photos, stats and details
Colour palette
Charcoal
#161616
Graphite
#262626
Neon lime
#D7F21C
Alert red
#FF4D4D
Silver
#BDBDBD
White
#FFFFFF
How to use this design
- 01
Pick what to copy
Copy the Adventure Travel Booking artifact, skill or code from this page.
- 02
Run it or generate it
Paste the code into a new Expo app and start it, or give the skill to your AI coding agent.
- 03
Make it yours
Replace the demo images and sample content, then connect your own data.
Frequently asked questions
Which parts are interactive?
The filter chips, tab bar and favourite hearts on the home screen hold state, and the detail screen heart toggles too.
Can I change the lime accent?
Yes. The accent is one value at the top of each screen, so a mint or orange theme is a quick edit.
Does it include a booking flow?
It stops at the Book Now action. Link that button to your own date picker and checkout screens.
Is the Adventure Travel Booking design free to use?
Yes. The design, skill and Expo code are free and MIT licensed, including for commercial apps. Replace the demo images and sample content with your own before you ship.
What is the difference between the artifact, skill and code?
The artifact is the full design: tokens, screens and source. The skill is a SKILL.md that teaches an AI coding agent to build the UI. The code is the finished Expo app you can paste in and run.
Which Expo version does the code use?
It targets Expo SDK 58 with Expo Router and TypeScript. It uses packages installed through npx expo install, with no custom native code.



