// Food app UI
Fast Food Ordering App UI Design in Expo
This food ordering app UI follows a hungry customer from a promo-led home screen through a filterable menu and a build-a-meal product page to a bag that totals itself.
- Screens
- 4
- Built with
- Expo SDK 58
- Fonts
- Plus Jakarta Sans, Pacifico
- License
- Free (MIT)

Crispy Coop Food Ordering app screens

Home and offers
A greeting, search, a limited-time hero, category shortcuts, popular picks and an exclusive offer banner above a five item tab bar.

Full menu
Category tabs filter a list of dishes, each with a rating, calories, price and a quick add button.

Build a meal
A large sandwich photo, rating and prep time, a three way side picker and a quantity stepper tied to the Add to Bag total.

My bag
Delivery or pickup, editable line items, a promo code field and a summary that recalculates fees and tax.
Copy the Crispy Coop Food Ordering 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
# Crispy Coop Food Ordering: design artifact
This food ordering app UI follows a hungry customer from a promo-led home screen through a filterable menu and a build-a-meal product page to a bag that totals itself.
- Type: Food mobile app UI
- Screens: 4
- 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/crispy-chicken-food-app-ui-design
## Design direction
Warm cream pages and a bold chicken-shop red, with food photography on rounded cards and a raised order button in the tab bar.
- Menu tabs that filter the item list
- Make-it-a-meal side picker with live pricing
- Delivery or pickup toggle that updates the total
- Raised order button in a five tab bar
## Foundations
### Colour
| Token | Hex |
| --- | --- |
| Coop red | `#C8102E` |
| Deep red | `#9E0B22` |
| Cream | `#F7F3EE` |
| Peach | `#F6E3D2` |
| Ink | `#1A1A1A` |
| Slate | `#5A5A5A` |
### Typography
- Typefaces: Plus Jakarta Sans, Pacifico
- Common sizes (px): 10, 11, 12, 13, 14, 15, 16, 20
### Shape and depth
- Common corner radii (px): 8, 12, 14, 16, 20, 32
- Shadows: `0 4px 14px rgba(60,30,10,0.07)`, `0 4px 14px rgba(60,30,10,0.06)`, `0 6px 18px rgba(60,30,10,0.06)`, `0 -2px 24px rgba(60,30,10,0.10)`
## Screens
### 1. Home and offers
A greeting, search, a limited-time hero, category shortcuts, popular picks and an exclusive offer banner above a five item tab bar.
Preview: https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/home.webp
Copy on this screen:
- Hi, Chicken Lover!
- Crispy Coop
- Crispy. Every time.
- 3
- 2
- Search
- LIMITED TIME
- Made Fresh.
- Made for You.
- Real chicken, real ingredients, made the Coop way.
- Order Now
- All
- Popular Picks
- View All
- EXCLUSIVE OFFER
- Up to 25% OFF
- On select meals
- 25%
- OFF
- Home
- Menu
- Order
- Rewards
- Profile
### 2. Full menu
Category tabs filter a list of dishes, each with a rating, calories, price and a quick add button.
Preview: https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/menu.webp
Copy on this screen:
- Our Menu
- 2
- Home
- Menu
- Order
- Rewards
- Profile
### 3. Build a meal
A large sandwich photo, rating and prep time, a three way side picker and a quantity stepper tied to the Add to Bag total.
Preview: https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/product.webp
Copy on this screen:
- BESTSELLER
- Coop Classic Chicken Sandwich
- 4.8 (12.5K+)
- 440 cal
- 8–10 min
- A boneless breast of chicken, seasoned to perfection, hand-breaded and pressure-fried, served on a toasted buttered brioche bun with dill pickle chips.
- Make it a meal
- −
- +
### 4. My bag
Delivery or pickup, editable line items, a promo code field and a summary that recalculates fees and tax.
Preview: https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/bag.webp
Copy on this screen:
- My Bag
- −
- +
- Promo code
- Apply
- Subtotal
- Tax (est.)
- Total
## Full source
The original artboards as HTML. Image URLs point at the hosted demo assets.
### Home and offers (Main)
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Crispy Coop Home</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Pacifico&display=swap" rel="stylesheet">
<style>
body{margin:0}
a{color:#C8102E}a:hover{color:#9E0B22}
</style>
</helmet>
<div style="width: 390px; height: 1120px; box-sizing: border-box; background: #F7F3EE; font-family: 'Plus Jakarta Sans', system-ui, sans-serif; color: #1A1A1A; position: relative; overflow: hidden">
<div style="padding: 20px 18px 0; display: flex; flex-direction: column; gap: 16px">
<div style="display: flex; align-items: center; gap: 12px">
<button aria-label="Open menu" style="width: 48px; height: 48px; border-radius: 16px; border: none; background: #FFFFFF; box-shadow: 0 4px 14px rgba(60,30,10,0.07); display: flex; align-items: center; justify-content: center; padding: 0">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"></path></svg>
</button>
<div style="flex: 1; display: flex; flex-direction: column; gap: 0">
<span style="font-size: 12px; font-weight: 500; color: #4A4A4A">Hi, Chicken Lover!</span>
<span style="font-family: 'Pacifico', cursive; font-size: 24px; line-height: 1.25; color: {{accent}}">Crispy Coop</span>
<span style="font-size: 11px; font-weight: 500; color: #4A4A4A">Crispy. Every time.</span>
</div>
<button aria-label="Notifications, 3 new" style="width: 48px; height: 48px; border-radius: 16px; border: none; background: #FFFFFF; box-shadow: 0 4px 14px rgba(60,30,10,0.07); display: flex; align-items: center; justify-content: center; padding: 0; position: relative">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"></path><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"></path></svg>
<span style="position: absolute; top: 8px; right: 8px; min-width: 16px; height: 16px; border-radius: 8px; background: {{accent}}; color: #FFFFFF; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center">3</span>
</button>
<button aria-label="Bag, 2 items" style="width: 48px; height: 48px; border-radius: 16px; border: none; background: #FFFFFF; box-shadow: 0 4px 14px rgba(60,30,10,0.07); display: flex; align-items: center; justify-content: center; padding: 0; position: relative">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 7h12l-1 13H7L6 7z"></path><path d="M9 7a3 3 0 0 1 6 0"></path></svg>
<span style="position: absolute; top: 8px; right: 8px; min-width: 16px; height: 16px; border-radius: 8px; background: {{accent}}; color: #FFFFFF; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center">2</span>
</button>
</div>
<div style="display: flex; gap: 12px; align-items: center">
<label style="flex: 1; height: 50px; border-radius: 16px; background: #FFFFFF; box-shadow: 0 4px 14px rgba(60,30,10,0.06); display: flex; align-items: center; gap: 10px; padding: 0 16px; box-sizing: border-box">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><path d="M20 20l-3.5-3.5"></path></svg>
<span style="position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0)">Search</span>
<input type="search" placeholder="Search your favorite meal..." style="border: none; outline: none; background: transparent; font-family: inherit; font-size: 14px; color: #1A1A1A; flex: 1; min-width: 0">
</label>
<button aria-label="Filters" style="width: 50px; height: 50px; border-radius: 16px; border: none; background: #FFFFFF; box-shadow: 0 4px 14px rgba(60,30,10,0.06); display: flex; align-items: center; justify-content: center; padding: 0">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M4 6h10M18 6h2M4 12h4M12 12h8M4 18h12M20 18h0"></path><circle cx="16" cy="6" r="2"></circle><circle cx="10" cy="12" r="2"></circle><circle cx="18" cy="18" r="2"></circle></svg>
</button>
</div>
<div style="position: relative; height: 222px; border-radius: 24px; background: #F6E3D2; overflow: hidden">
<img src="https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/f5b6715188.jpg" alt="Chicken sandwich meal with waffle fries and a drink" style="position: absolute; right: -36px; top: 18px; width: 252px; height: 190px; object-fit: cover; border-radius: 0 0 0 0">
<div style="position: absolute; left: 0; top: 0; bottom: 0; width: 200px; background: linear-gradient(90deg, #F6E3D2 70%, rgba(246,227,210,0))"></div>
<div style="position: absolute; left: 18px; top: 18px; width: 184px; display: flex; flex-direction: column; gap: 8px">
<span style="align-self: flex-start; font-size: 10px; font-weight: 700; letter-spacing: 0.06em; color: #A30D24; background: #FBD6D0; padding: 4px 9px; border-radius: 999px">LIMITED TIME</span>
<span style="font-size: 25px; line-height: 1.1; font-weight: 800; color: #1A1A1A">Made Fresh.<br><span style="color: {{accent}}">Made for You.</span></span>
<span style="font-size: 11.5px; line-height: 1.45; color: #3A3A3A">Real chicken, real ingredients, made the Coop way.</span>
<a href="Product.dc.html" style="align-self: flex-start; margin-top: 2px; display: flex; align-items: center; gap: 10px; background: {{accent}}; color: #FFFFFF; text-decoration: none; font-size: 13px; font-weight: 600; height: 40px; padding: 0 6px 0 16px; border-radius: 999px">Order Now
<span style="width: 28px; height: 28px; border-radius: 14px; background: #FFFFFF; display: flex; align-items: center; justify-content: center"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#C8102E" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"></path></svg></span>
</a>
</div>
</div>
</div>
<div style="display: flex; gap: 12px; padding: 18px 18px 0; overflow: hidden">
<a href="Menu.dc.html" style="display: flex; flex-direction: column; align-items: center; gap: 8px; text-decoration: none; color: #C8102E; flex: none">
<span style="width: 64px; height: 64px; border-radius: 32px; border: 2px solid {{accent}}; background: #FFFFFF; display: flex; align-items: center; justify-content: center; box-sizing: border-box"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#C8102E" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="4" width="6" height="6" rx="1.5"></rect><rect x="14" y="4" width="6" height="6" rx="1.5"></rect><rect x="4" y="14" width="6" height="6" rx="1.5"></rect><rect x="14" y="14" width="6" height="6" rx="1.5"></rect></svg></span>
<span style="font-size: 12px; font-weight: 600">All</span>
</a>
<sc-for list="{{cats}}" as="c" hint-placeholder-count="5">
<a href="Menu.dc.html" style="display: flex; flex-direction: column; align-items: center; gap: 8px; text-decoration: none; color: #2A2A2A; flex: none">
<span style="width: 64px; height: 64px; border-radius: 32px; background: #FFFFFF; box-shadow: 0 4px 12px rgba(60,30,10,0.06); overflow: hidden; display: flex; align-items: center; justify-content: center"><img src="{{c.img}}" alt="" style="width: 54px; height: 54px; object-fit: contain"></span>
<span style="font-size: 12px; font-weight: 500">{{c.name}}</span>
</a>
</sc-for>
</div>
<div style="padding: 22px 18px 0; display: flex; justify-content: space-between; align-items: center">
<h2 style="margin: 0; font-size: 18px; font-weight: 700">Popular Picks</h2>
<a href="Menu.dc.html" style="font-size: 13px; font-weight: 600; text-decoration: none; display: flex; align-items: center; gap: 6px">View All <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"></path></svg></a>
</div>
<div style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; padding: 12px 18px 0">
<sc-for list="{{picks}}" as="p" hint-placeholder-count="3">
<div style="background: #FFFFFF; border-radius: 18px; box-shadow: 0 6px 18px rgba(60,30,10,0.06); padding: 10px; display: flex; flex-direction: column; gap: 6px">
<div style="display: flex; justify-content: space-between; align-items: center">
<span style="font-size: 8.5px; font-weight: 700; letter-spacing: 0.04em; color: #FFFFFF; background: {{p.tagBg}}; padding: 3px 7px; border-radius: 999px">{{p.tag}}</span>
<button aria-label="Save to favorites" style="width: 24px; height: 24px; border: none; background: transparent; padding: 0; display: flex; align-items: center; justify-content: center"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#6B6B6B" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z"></path></svg></button>
</div>
<img src="{{p.img}}" alt="{{p.name}}" style="width: 100%; height: 80px; object-fit: contain">
<a href="Product.dc.html" style="font-size: 12px; font-weight: 700; line-height: 1.25; color: #1A1A1A; text-decoration: none; min-height: 30px">{{p.name}}</a>
<span style="font-size: 9.5px; line-height: 1.35; color: #5A5A5A">{{p.desc}}</span>
<span style="font-size: 10px; color: #4A4A4A; display: flex; align-items: center; gap: 3px"><svg width="11" height="11" viewBox="0 0 24 24" fill="#F2A21A" aria-hidden="true"><path d="M12 3l2.7 5.6 6.1.9-4.4 4.3 1 6.1L12 17l-5.4 2.9 1-6.1L3.2 9.5l6.1-.9z"></path></svg>{{p.rating}}</span>
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 2px">
<span style="font-size: 15px; font-weight: 800">{{p.price}}</span>
<button aria-label="Add to bag" style="width: 28px; height: 28px; border-radius: 14px; border: none; background: {{accent}}; padding: 0; display: flex; align-items: center; justify-content: center"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"></path></svg></button>
</div>
</div>
</sc-for>
</div>
<div style="margin: 16px 18px 0; position: relative; height: 128px; border-radius: 22px; background: #F6E3D2; overflow: hidden">
<img src="https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/f5b6715188.jpg" alt="Chicken meal combo" style="position: absolute; left: 150px; top: 6px; width: 150px; height: 116px; object-fit: cover; border-radius: 14px">
<div style="position: absolute; left: 18px; top: 16px; display: flex; flex-direction: column; gap: 4px">
<span style="font-size: 9.5px; font-weight: 700; letter-spacing: 0.06em; color: #A30D24">EXCLUSIVE OFFER</span>
<span style="font-size: 19px; font-weight: 800">Up to 25% OFF</span>
<span style="font-size: 11px; color: #3A3A3A">On select meals</span>
<a href="Menu.dc.html" style="margin-top: 4px; align-self: flex-start; display: flex; align-items: center; gap: 6px; background: {{accent}}; color: #FFFFFF; text-decoration: none; font-size: 11px; font-weight: 600; height: 28px; padding: 0 4px 0 12px; border-radius: 999px">Order Now <span style="width: 20px; height: 20px; border-radius: 10px; background: #FFFFFF; display: flex; align-items: center; justify-content: center"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#C8102E" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"></path></svg></span></a>
</div>
<span style="position: absolute; right: 12px; top: 24px; width: 72px; height: 72px; border-radius: 36px; background: {{accent}}; color: #FFFFFF; display: flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1"><span style="font-size: 22px; font-weight: 800">25%</span><span style="font-size: 12px; font-weight: 700; margin-top: 2px">OFF</span></span>
</div>
<nav aria-label="Main" style="position: absolute; left: 12px; right: 12px; bottom: 14px; height: 72px; border-radius: 26px; background: #FFFFFF; box-shadow: 0 -2px 24px rgba(60,30,10,0.10); display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); align-items: end; padding-bottom: 10px; box-sizing: border-box">
<a href="Main.dc.html" style="display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; text-decoration: none; color: #C8102E"><svg width="22" height="22" viewBox="0 0 24 24" fill="#C8102E" aria-hidden="true"><path d="M3 11l9-7 9 7v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"></path></svg>Home</a>
<a href="Menu.dc.html" style="display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 11px; font-weight: 500; text-decoration: none; color: #5A5A5A"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#5A5A5A" stroke-width="1.8" aria-hidden="true"><rect x="4" y="4" width="6.5" height="6.5" rx="1.5"></rect><rect x="13.5" y="4" width="6.5" height="6.5" rx="1.5"></rect><rect x="4" y="13.5" width="6.5" height="6.5" rx="1.5"></rect><rect x="13.5" y="13.5" width="6.5" height="6.5" rx="1.5"></rect></svg>Menu</a>
<a href="Cart.dc.html" style="display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; text-decoration: none; color: #FFFFFF; position: relative; top: -2px"><span style="width: 64px; height: 64px; border-radius: 32px; background: {{accent}}; border: 5px solid #F7F3EE; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; margin-top: -30px; box-shadow: 0 8px 18px rgba(200,16,46,0.35)"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 7h12l-1 13H7L6 7z"></path><path d="M9 7a3 3 0 0 1 6 0"></path></svg><span style="font-size: 10px">Order</span></span></a>
<a href="Main.dc.html" style="display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 11px; font-weight: 500; text-decoration: none; color: #5A5A5A"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#5A5A5A" stroke-width="1.8" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="9" width="17" height="11" rx="1.5"></rect><path d="M3.5 13h17M12 9v11M12 9c-2-4-6-4-6-1.5S10 9 12 9zM12 9c2-4 6-4 6-1.5S14 9 12 9z"></path></svg>Rewards</a>
<a href="Main.dc.html" style="display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 11px; font-weight: 500; text-decoration: none; color: #5A5A5A"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#5A5A5A" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="8" r="4"></circle><path d="M4 21c1.5-4 4.5-6 8-6s6.5 2 8 6"></path></svg>Profile</a>
</nav>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"accent":{"editor":"color","default":"#C8102E","options":["#C8102E","#E4572E","#1F7A4D","#1D4ED8"]},"$preview":{"width":390,"height":1120}}'>
class Component extends DCLogic {
renderVals() {
var a = this.props.accent ?? '#C8102E';
return {
accent: a,
cats: [
{ name: 'Chicken', img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/d6c3851965.jpg' },
{ name: 'Sandwiches', img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/3fc3e32c9f.jpg' },
{ name: 'Salads', img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/f250ab5eaf.jpg' },
{ name: 'Drinks', img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/718d422cc9.jpg' },
{ name: 'Treats', img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/ee83c21209.jpg' }
],
picks: [
{ tag: 'BESTSELLER', tagBg: a, name: 'Coop Classic Chicken Sandwich', desc: 'A boneless breast of chicken seasoned to perfection', rating: '4.8 (12.5K+)', price: '$6.79', img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/3fc3e32c9f.jpg' },
{ tag: 'POPULAR', tagBg: '#B45309', name: 'Crispy Nuggets (8 ct)', desc: 'Bite-sized perfection with your favorite dipping sauce', rating: '4.7 (8.7K+)', price: '$5.89', img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/d6c3851965.jpg' },
{ tag: 'SAVE 15%', tagBg: a, name: 'Waffle Potato Fries', desc: 'Crispy on the outside, tender on the inside', rating: '4.6 (6.3K+)', price: '$2.19', img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/e6a94cba21.jpg' }
]
};
}
}
</script>
</body>
</html>
```
### Full menu (Menu)
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Crispy Coop Menu</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
body{margin:0}
a{color:#C8102E}a:hover{color:#9E0B22}
</style>
</helmet>
<div style="width: 390px; height: 844px; box-sizing: border-box; background: #F7F3EE; font-family: 'Plus Jakarta Sans', system-ui, sans-serif; color: #1A1A1A; position: relative; overflow: hidden">
<div style="padding: 20px 18px 0; display: flex; align-items: center; gap: 12px">
<a href="Main.dc.html" aria-label="Back to home" style="width: 48px; height: 48px; border-radius: 16px; background: #FFFFFF; box-shadow: 0 4px 14px rgba(60,30,10,0.07); display: flex; align-items: center; justify-content: center"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 5l-7 7 7 7"></path></svg></a>
<h1 style="flex: 1; margin: 0; font-size: 20px; font-weight: 800; text-align: center">Our Menu</h1>
<a href="Cart.dc.html" aria-label="Bag, 2 items" style="width: 48px; height: 48px; border-radius: 16px; background: #FFFFFF; box-shadow: 0 4px 14px rgba(60,30,10,0.07); display: flex; align-items: center; justify-content: center; position: relative"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 7h12l-1 13H7L6 7z"></path><path d="M9 7a3 3 0 0 1 6 0"></path></svg><span style="position: absolute; top: 8px; right: 8px; min-width: 16px; height: 16px; border-radius: 8px; background: {{accent}}; color: #FFFFFF; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center">2</span></a>
</div>
<div style="display: flex; gap: 8px; padding: 18px 18px 0; overflow: hidden">
<sc-for list="{{tabs}}" as="t" hint-placeholder-count="5">
<button onClick="{{t.pick}}" style="flex: none; height: 40px; padding: 0 16px; border-radius: 999px; border: none; font-family: inherit; font-size: 13px; font-weight: 600; background: {{t.bg}}; color: {{t.fg}}; box-shadow: 0 3px 10px rgba(60,30,10,0.06)">{{t.name}}</button>
</sc-for>
</div>
<div style="padding: 18px 18px 0; display: flex; justify-content: space-between; align-items: baseline">
<h2 style="margin: 0; font-size: 17px; font-weight: 700">{{active}}</h2>
<span style="font-size: 12px; color: #5A5A5A">{{count}} items</span>
</div>
<div style="display: flex; flex-direction: column; gap: 12px; padding: 12px 18px 0">
<sc-for list="{{items}}" as="it" hint-placeholder-count="4">
<a href="Product.dc.html" style="display: flex; gap: 14px; align-items: center; background: #FFFFFF; border-radius: 20px; padding: 12px; box-shadow: 0 6px 18px rgba(60,30,10,0.06); text-decoration: none; color: #1A1A1A">
<span style="width: 88px; height: 88px; border-radius: 16px; background: #F6E3D2; flex: none; display: flex; align-items: center; justify-content: center; overflow: hidden"><img src="{{it.img}}" alt="" style="width: 80px; height: 80px; object-fit: contain; mix-blend-mode: multiply"></span>
<span style="flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px">
<span style="font-size: 14px; font-weight: 700">{{it.name}}</span>
<span style="font-size: 11.5px; line-height: 1.4; color: #5A5A5A">{{it.desc}}</span>
<span style="display: flex; align-items: center; gap: 10px; font-size: 11px; color: #4A4A4A"><span style="display: flex; align-items: center; gap: 3px"><svg width="11" height="11" viewBox="0 0 24 24" fill="#F2A21A" aria-hidden="true"><path d="M12 3l2.7 5.6 6.1.9-4.4 4.3 1 6.1L12 17l-5.4 2.9 1-6.1L3.2 9.5l6.1-.9z"></path></svg>{{it.rating}}</span><span>{{it.cal}}</span></span>
<span style="display: flex; justify-content: space-between; align-items: center; margin-top: 2px"><span style="font-size: 16px; font-weight: 800">{{it.price}}</span><span aria-hidden="true" style="width: 32px; height: 32px; border-radius: 16px; background: {{accent}}; display: flex; align-items: center; justify-content: center"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round"><path d="M12 5v14M5 12h14"></path></svg></span></span>
</span>
</a>
</sc-for>
</div>
<nav aria-label="Main" style="position: absolute; left: 12px; right: 12px; bottom: 14px; height: 72px; border-radius: 26px; background: #FFFFFF; box-shadow: 0 -2px 24px rgba(60,30,10,0.10); display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); align-items: end; padding-bottom: 10px; box-sizing: border-box">
<a href="Main.dc.html" style="display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 11px; font-weight: 500; text-decoration: none; color: #5A5A5A"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#5A5A5A" stroke-width="1.8" stroke-linejoin="round" aria-hidden="true"><path d="M3 11l9-7 9 7v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"></path></svg>Home</a>
<a href="Menu.dc.html" style="display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; text-decoration: none; color: #C8102E"><svg width="22" height="22" viewBox="0 0 24 24" fill="#C8102E" aria-hidden="true"><rect x="4" y="4" width="6.5" height="6.5" rx="1.5"></rect><rect x="13.5" y="4" width="6.5" height="6.5" rx="1.5"></rect><rect x="4" y="13.5" width="6.5" height="6.5" rx="1.5"></rect><rect x="13.5" y="13.5" width="6.5" height="6.5" rx="1.5"></rect></svg>Menu</a>
<a href="Cart.dc.html" style="display: flex; flex-direction: column; align-items: center; text-decoration: none; color: #FFFFFF; position: relative; top: -2px"><span style="width: 64px; height: 64px; border-radius: 32px; background: {{accent}}; border: 5px solid #F7F3EE; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; margin-top: -30px; box-shadow: 0 8px 18px rgba(200,16,46,0.35)"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 7h12l-1 13H7L6 7z"></path><path d="M9 7a3 3 0 0 1 6 0"></path></svg><span style="font-size: 10px; font-weight: 600">Order</span></span></a>
<a href="Main.dc.html" style="display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 11px; font-weight: 500; text-decoration: none; color: #5A5A5A"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#5A5A5A" stroke-width="1.8" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="9" width="17" height="11" rx="1.5"></rect><path d="M3.5 13h17M12 9v11M12 9c-2-4-6-4-6-1.5S10 9 12 9zM12 9c2-4 6-4 6-1.5S14 9 12 9z"></path></svg>Rewards</a>
<a href="Main.dc.html" style="display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 11px; font-weight: 500; text-decoration: none; color: #5A5A5A"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#5A5A5A" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="8" r="4"></circle><path d="M4 21c1.5-4 4.5-6 8-6s6.5 2 8 6"></path></svg>Profile</a>
</nav>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"accent":{"editor":"color","default":"#C8102E","options":["#C8102E","#E4572E","#1F7A4D","#1D4ED8"]},"$preview":{"width":390,"height":844}}'>
class Component extends DCLogic {
renderVals() {
var a = this.props.accent ?? '#C8102E';
var self = this;
var active = (this.state && this.state.tab) || 'All';
var S = 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/3fc3e32c9f.jpg', N = 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/d6c3851965.jpg', F = 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/e6a94cba21.jpg', D = 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/718d422cc9.jpg', L = 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/f250ab5eaf.jpg', C = 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/ee83c21209.jpg';
var all = [
{ cat: 'Sandwiches', name: 'Coop Classic Sandwich', desc: 'Pressure-fried chicken breast, pickles, toasted brioche', rating: '4.8', cal: '440 cal', price: '$6.79', img: S },
{ cat: 'Chicken', name: 'Crispy Nuggets (8 ct)', desc: 'Bite-sized, hand-breaded, with a dipping sauce', rating: '4.7', cal: '250 cal', price: '$5.89', img: N },
{ cat: 'Salads', name: 'Southwest Chicken Salad', desc: 'Grilled chicken, corn, tomato, cheese, tortilla strips', rating: '4.6', cal: '450 cal', price: '$9.49', img: L },
{ cat: 'Chicken', name: 'Waffle Potato Fries', desc: 'Crispy outside, tender inside, sea salt', rating: '4.6', cal: '420 cal', price: '$2.19', img: F },
{ cat: 'Drinks', name: 'Fresh Lemonade', desc: 'Squeezed daily, lightly sweet', rating: '4.7', cal: '220 cal', price: '$2.49', img: D },
{ cat: 'Treats', name: 'Vanilla Soft-Serve Cone', desc: 'Creamy swirl in a crunchy wafer cone', rating: '4.8', cal: '170 cal', price: '$1.89', img: C }
];
var items = active === 'All' ? all.slice(0, 4) : all.filter(function (x) { return x.cat === active; });
var tabs = ['All', 'Chicken', 'Sandwiches', 'Salads', 'Drinks', 'Treats'].map(function (n) {
return { name: n, bg: n === active ? a : '#FFFFFF', fg: n === active ? '#FFFFFF' : '#2A2A2A', pick: function () { self.setState({ tab: n }); } };
});
return { accent: a, tabs: tabs, items: items, active: active === 'All' ? 'All items' : active, count: active === 'All' ? all.length : items.length };
}
}
</script>
</body>
</html>
```
### Build a meal (Product)
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Coop Classic Sandwich</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
body{margin:0}
a{color:#C8102E}a:hover{color:#9E0B22}
</style>
</helmet>
<div style="width: 390px; height: 844px; box-sizing: border-box; background: #F7F3EE; font-family: 'Plus Jakarta Sans', system-ui, sans-serif; color: #1A1A1A; position: relative; overflow: hidden">
<div style="position: absolute; left: 0; right: 0; top: 0; height: 360px; background: #F6E3D2; border-radius: 0 0 36px 36px"></div>
<div style="position: relative; padding: 20px 18px 0; display: flex; justify-content: space-between">
<a href="Menu.dc.html" aria-label="Back to menu" style="width: 48px; height: 48px; border-radius: 16px; background: #FFFFFF; box-shadow: 0 4px 14px rgba(60,30,10,0.07); display: flex; align-items: center; justify-content: center"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 5l-7 7 7 7"></path></svg></a>
<button onClick="{{toggleFav}}" aria-label="Save to favorites" style="width: 48px; height: 48px; border-radius: 16px; border: none; background: #FFFFFF; box-shadow: 0 4px 14px rgba(60,30,10,0.07); display: flex; align-items: center; justify-content: center; padding: 0"><svg width="22" height="22" viewBox="0 0 24 24" fill="{{favFill}}" stroke="{{favStroke}}" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z"></path></svg></button>
</div>
<img src="https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/3fc3e32c9f.jpg" alt="Coop Classic Chicken Sandwich" style="position: relative; display: block; margin: 0 auto; width: 270px; height: 250px; object-fit: contain; mix-blend-mode: multiply">
<div style="position: relative; padding: 22px 18px 0; display: flex; flex-direction: column; gap: 10px">
<div style="display: flex; justify-content: space-between; align-items: flex-start; gap: 12px">
<div style="display: flex; flex-direction: column; gap: 6px">
<span style="align-self: flex-start; font-size: 10px; font-weight: 700; letter-spacing: 0.05em; color: #FFFFFF; background: {{accent}}; padding: 4px 9px; border-radius: 999px">BESTSELLER</span>
<h1 style="margin: 0; font-size: 22px; font-weight: 800; line-height: 1.2">Coop Classic Chicken Sandwich</h1>
</div>
<span style="font-size: 22px; font-weight: 800; color: {{accent}}">{{unit}}</span>
</div>
<div style="display: flex; gap: 14px; font-size: 12px; color: #4A4A4A">
<span style="display: flex; align-items: center; gap: 4px"><svg width="13" height="13" viewBox="0 0 24 24" fill="#F2A21A" aria-hidden="true"><path d="M12 3l2.7 5.6 6.1.9-4.4 4.3 1 6.1L12 17l-5.4 2.9 1-6.1L3.2 9.5l6.1-.9z"></path></svg>4.8 (12.5K+)</span>
<span>440 cal</span>
<span style="display: flex; align-items: center; gap: 4px"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#4A4A4A" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg>8–10 min</span>
</div>
<p style="margin: 0; font-size: 13px; line-height: 1.55; color: #3A3A3A">A boneless breast of chicken, seasoned to perfection, hand-breaded and pressure-fried, served on a toasted buttered brioche bun with dill pickle chips.</p>
<h2 style="margin: 6px 0 0; font-size: 15px; font-weight: 700">Make it a meal</h2>
<div style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px">
<sc-for list="{{sides}}" as="s" hint-placeholder-count="3">
<button onClick="{{s.pick}}" style="border: 2px solid {{s.border}}; background: #FFFFFF; border-radius: 16px; padding: 8px 6px; display: flex; flex-direction: column; align-items: center; gap: 4px; font-family: inherit; color: #1A1A1A">
<img src="{{s.img}}" alt="" style="width: 52px; height: 52px; object-fit: contain">
<span style="font-size: 11.5px; font-weight: 600">{{s.name}}</span>
<span style="font-size: 11px; color: #5A5A5A">{{s.price}}</span>
</button>
</sc-for>
</div>
</div>
<div style="position: absolute; left: 0; right: 0; bottom: 0; padding: 14px 18px 22px; background: #FFFFFF; border-radius: 28px 28px 0 0; box-shadow: 0 -6px 24px rgba(60,30,10,0.08); display: flex; align-items: center; gap: 14px">
<div style="display: flex; align-items: center; gap: 4px; background: #F7F3EE; border-radius: 999px; padding: 4px">
<button onClick="{{dec}}" aria-label="Decrease quantity" style="width: 40px; height: 40px; border-radius: 20px; border: none; background: #FFFFFF; font-size: 20px; font-weight: 700; color: #1A1A1A; padding: 0">−</button>
<span style="min-width: 24px; text-align: center; font-size: 16px; font-weight: 700">{{qty}}</span>
<button onClick="{{inc}}" aria-label="Increase quantity" style="width: 40px; height: 40px; border-radius: 20px; border: none; background: {{accent}}; font-size: 20px; font-weight: 700; color: #FFFFFF; padding: 0">+</button>
</div>
<a href="Cart.dc.html" style="flex: 1; height: 52px; border-radius: 999px; background: {{accent}}; color: #FFFFFF; text-decoration: none; display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 15px; font-weight: 700">Add to Bag · {{total}}</a>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"accent":{"editor":"color","default":"#C8102E","options":["#C8102E","#E4572E","#1F7A4D","#1D4ED8"]},"$preview":{"width":390,"height":844}}'>
class Component extends DCLogic {
renderVals() {
var a = this.props.accent ?? '#C8102E';
var self = this;
var st = this.state || {};
var qty = st.qty || 1;
var side = st.side || 'Fries';
var fav = !!st.fav;
var base = 6.79;
var opts = [
{ name: 'Fries', price: 2.19, img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/e6a94cba21.jpg' },
{ name: 'Drink', price: 2.49, img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/718d422cc9.jpg' },
{ name: 'Nuggets', price: 3.29, img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/d6c3851965.jpg' }
];
var extra = 0;
var sides = opts.map(function (o) {
if (o.name === side) extra = o.price;
return { name: o.name, img: o.img, price: '+$' + o.price.toFixed(2), border: o.name === side ? a : '#EFE6DC', pick: function () { self.setState({ side: side === o.name ? 'none' : o.name }); } };
});
return {
accent: a,
qty: qty,
unit: '$' + base.toFixed(2),
total: '$' + ((base + extra) * qty).toFixed(2),
sides: sides,
favFill: fav ? a : 'none',
favStroke: fav ? a : '#1A1A1A',
toggleFav: function () { self.setState({ fav: !fav }); },
inc: function () { self.setState({ qty: Math.min(qty + 1, 20) }); },
dec: function () { self.setState({ qty: Math.max(qty - 1, 1) }); }
};
}
}
</script>
</body>
</html>
```
### My bag (Cart)
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Crispy Coop Bag</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
body{margin:0}
a{color:#C8102E}a:hover{color:#9E0B22}
</style>
</helmet>
<div style="width: 390px; height: 844px; box-sizing: border-box; background: #F7F3EE; font-family: 'Plus Jakarta Sans', system-ui, sans-serif; color: #1A1A1A; position: relative; overflow: hidden">
<div style="padding: 20px 18px 0; display: flex; align-items: center; gap: 12px">
<a href="Product.dc.html" aria-label="Back" style="width: 48px; height: 48px; border-radius: 16px; background: #FFFFFF; box-shadow: 0 4px 14px rgba(60,30,10,0.07); display: flex; align-items: center; justify-content: center"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 5l-7 7 7 7"></path></svg></a>
<h1 style="flex: 1; margin: 0; font-size: 20px; font-weight: 800; text-align: center">My Bag</h1>
<span style="width: 48px"></span>
</div>
<div style="margin: 18px 18px 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); background: #FFFFFF; border-radius: 16px; padding: 4px; box-shadow: 0 4px 14px rgba(60,30,10,0.05)">
<sc-for list="{{modes}}" as="m" hint-placeholder-count="2">
<button onClick="{{m.pick}}" style="height: 40px; border: none; border-radius: 12px; font-family: inherit; font-size: 13px; font-weight: 600; background: {{m.bg}}; color: {{m.fg}}">{{m.name}}</button>
</sc-for>
</div>
<div style="display: flex; flex-direction: column; gap: 10px; padding: 14px 18px 0">
<sc-for list="{{lines}}" as="l" hint-placeholder-count="3">
<div style="display: flex; gap: 12px; align-items: center; background: #FFFFFF; border-radius: 18px; padding: 10px; box-shadow: 0 6px 18px rgba(60,30,10,0.05)">
<span style="width: 64px; height: 64px; border-radius: 14px; background: #F6E3D2; flex: none; display: flex; align-items: center; justify-content: center"><img src="{{l.img}}" alt="" style="width: 56px; height: 56px; object-fit: contain; mix-blend-mode: multiply"></span>
<span style="flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px">
<span style="font-size: 13.5px; font-weight: 700">{{l.name}}</span>
<span style="font-size: 11px; color: #5A5A5A">{{l.note}}</span>
<span style="font-size: 14px; font-weight: 800">{{l.lineTotal}}</span>
</span>
<span style="display: flex; align-items: center; gap: 2px; background: #F7F3EE; border-radius: 999px; padding: 3px">
<button onClick="{{l.dec}}" aria-label="Remove one" style="width: 30px; height: 30px; border-radius: 15px; border: none; background: #FFFFFF; font-size: 16px; font-weight: 700; padding: 0; color: #1A1A1A">−</button>
<span style="min-width: 20px; text-align: center; font-size: 13px; font-weight: 700">{{l.qty}}</span>
<button onClick="{{l.inc}}" aria-label="Add one" style="width: 30px; height: 30px; border-radius: 15px; border: none; background: {{accent}}; font-size: 16px; font-weight: 700; padding: 0; color: #FFFFFF">+</button>
</span>
</div>
</sc-for>
</div>
<label style="margin: 14px 18px 0; height: 50px; border-radius: 16px; background: #FFFFFF; border: 1.5px dashed #E2C9B4; display: flex; align-items: center; gap: 10px; padding: 0 6px 0 14px; box-sizing: border-box">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#C8102E" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 9V6a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v3a3 3 0 0 0 0 6v3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-3a3 3 0 0 0 0-6z"></path><path d="M10 9l4 6"></path></svg>
<span style="position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0)">Promo code</span>
<input type="text" placeholder="Add promo code" style="flex: 1; min-width: 0; border: none; outline: none; background: transparent; font-family: inherit; font-size: 13px; color: #1A1A1A">
<button style="height: 38px; padding: 0 14px; border-radius: 12px; border: none; background: #FBD6D0; color: #A30D24; font-family: inherit; font-size: 12.5px; font-weight: 700">Apply</button>
</label>
<div style="position: absolute; left: 0; right: 0; bottom: 0; padding: 18px 18px 22px; background: #FFFFFF; border-radius: 28px 28px 0 0; box-shadow: 0 -6px 24px rgba(60,30,10,0.08); display: flex; flex-direction: column; gap: 9px">
<div style="display: flex; justify-content: space-between; font-size: 13px; color: #4A4A4A"><span>Subtotal</span><span>{{subtotal}}</span></div>
<div style="display: flex; justify-content: space-between; font-size: 13px; color: #4A4A4A"><span>{{feeLabel}}</span><span>{{fee}}</span></div>
<div style="display: flex; justify-content: space-between; font-size: 13px; color: #4A4A4A"><span>Tax (est.)</span><span>{{tax}}</span></div>
<div style="height: 1px; background: #EFE6DC; margin: 2px 0"></div>
<div style="display: flex; justify-content: space-between; font-size: 17px; font-weight: 800"><span>Total</span><span>{{total}}</span></div>
<button style="margin-top: 6px; height: 54px; border-radius: 999px; border: none; background: {{accent}}; color: #FFFFFF; font-family: inherit; font-size: 15px; font-weight: 700; display: flex; align-items: center; justify-content: center; gap: 10px">Checkout · {{total}}<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"></path></svg></button>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"accent":{"editor":"color","default":"#C8102E","options":["#C8102E","#E4572E","#1F7A4D","#1D4ED8"]},"$preview":{"width":390,"height":844}}'>
class Component extends DCLogic {
renderVals() {
var a = this.props.accent ?? '#C8102E';
var self = this;
var st = this.state || {};
var mode = st.mode || 'Delivery';
var q = st.q || { s: 1, n: 1, f: 2 };
var items = [
{ k: 's', name: 'Coop Classic Sandwich', note: 'Extra pickles', price: 6.79, img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/3fc3e32c9f.jpg' },
{ k: 'n', name: 'Crispy Nuggets (8 ct)', note: 'Honey mustard sauce', price: 5.89, img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/d6c3851965.jpg' },
{ k: 'f', name: 'Waffle Potato Fries', note: 'Medium', price: 2.19, img: 'https://developermill.com/app-designs/crispy-chicken-food-app-ui-design/assets/e6a94cba21.jpg' }
];
var sub = 0;
var lines = items.map(function (it) {
var n = q[it.k];
sub += n * it.price;
return {
name: it.name, note: it.note, img: it.img, qty: n, lineTotal: '$' + (n * it.price).toFixed(2),
inc: function () { var c = Object.assign({}, q); c[it.k] = Math.min(n + 1, 20); self.setState({ q: c }); },
dec: function () { var c = Object.assign({}, q); c[it.k] = Math.max(n - 1, 1); self.setState({ q: c }); }
};
});
var fee = mode === 'Delivery' ? 2.99 : 0;
var tax = sub * 0.08;
var modes = ['Delivery', 'Pickup'].map(function (m) {
return { name: m, bg: m === mode ? a : '#FFFFFF', fg: m === mode ? '#FFFFFF' : '#2A2A2A', pick: function () { self.setState({ mode: m }); } };
});
return {
accent: a, lines: lines, modes: modes,
subtotal: '$' + sub.toFixed(2),
feeLabel: mode === 'Delivery' ? 'Delivery fee' : 'Pickup',
fee: fee ? '$' + fee.toFixed(2) : 'Free',
tax: '$' + tax.toFixed(2),
total: '$' + (sub + fee + tax).toFixed(2)
};
}
}
</script>
</body>
</html>
```SKILL.md
Save as .claude/skills/crispy-chicken-food-app-ui/SKILL.md
--- name: crispy-chicken-food-app-ui description: Build the Crispy Coop Food Ordering mobile app UI in Expo React Native. Use when asked to create a food ordering app UI or a new screen in this visual style. --- # Crispy Coop Food Ordering app UI Build this food app UI in an Expo Router project with TypeScript. Reference: https://developermill.com/app-designs/crispy-chicken-food-app-ui-design ## Visual direction Warm cream pages and a bold chicken-shop red, with food photography on rounded cards and a raised order button in the tab bar. Keep these signature details: - Menu tabs that filter the item list - Make-it-a-meal side picker with live pricing - Delivery or pickup toggle that updates the total - Raised order button in a five tab bar ## Set up ```bash npx expo install expo-image expo-linear-gradient react-native-svg @expo-google-fonts/pacifico @expo-google-fonts/plus-jakarta-sans expo-font ``` ## Tokens | Name | Hex | | --- | --- | | Coop red | `#C8102E` | | Deep red | `#9E0B22` | | Cream | `#F7F3EE` | | Peach | `#F6E3D2` | | Ink | `#1A1A1A` | | Slate | `#5A5A5A` | - Typefaces: Plus Jakarta Sans, Pacifico - Text sizes (px): 10, 11, 12, 13, 14, 15, 16, 20 - Corner radii (px): 8, 12, 14, 16, 20, 32 Define these once and reuse them. Do not add new colours. ## Screens 1. **Home and offers.** A greeting, search, a limited-time hero, category shortcuts, popular picks and an exclusive offer banner above a five item tab bar. 2. **Full menu.** Category tabs filter a list of dishes, each with a rating, calories, price and a quick add button. 3. **Build a meal.** A large sandwich photo, rating and prep time, a three way side picker and a quantity stepper tied to the Add to Bag total. 4. **My bag.** Delivery or pickup, editable line items, a promo code field and a summary that recalculates fees and tax. ## 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 crispy-chicken-food-app --template default cd crispy-chicken-food-app rm -rf src npx expo install expo-image expo-linear-gradient react-native-svg @expo-google-fonts/pacifico @expo-google-fonts/plus-jakarta-sans expo-font
src/app/_layout.tsx
import { Pacifico_400Regular } from '@expo-google-fonts/pacifico';
import {
PlusJakartaSans_400Regular,
PlusJakartaSans_500Medium,
PlusJakartaSans_600SemiBold,
PlusJakartaSans_700Bold,
PlusJakartaSans_800ExtraBold,
} from '@expo-google-fonts/plus-jakarta-sans';
import { useFonts } from 'expo-font';
import { Stack } from 'expo-router';
export default function RootLayout() {
const [fontsLoaded] = useFonts({
Pacifico_400Regular,
PlusJakartaSans_400Regular,
PlusJakartaSans_500Medium,
PlusJakartaSans_600SemiBold,
PlusJakartaSans_700Bold,
PlusJakartaSans_800ExtraBold,
});
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, TextInput, View } from 'react-native';
import Svg, { Circle, Path, Rect } from 'react-native-svg';
import { Screen } from '@/components/screen';
import { asset } from '@/lib/assets';
const accent = '#C8102E';
const cats = [
{ name: 'Chicken', img: 'd6c3851965.jpg' },
{ name: 'Sandwiches', img: '3fc3e32c9f.jpg' },
{ name: 'Salads', img: 'f250ab5eaf.jpg' },
{ name: 'Drinks', img: '718d422cc9.jpg' },
{ name: 'Treats', img: 'ee83c21209.jpg' },
];
const picks = [
{
tag: 'BESTSELLER',
tagBg: '#C8102E',
name: 'Coop Classic Chicken Sandwich',
desc: 'A boneless breast of chicken seasoned to perfection',
rating: '4.8 (12.5K+)',
price: '$6.79',
img: '3fc3e32c9f.jpg',
},
{
tag: 'POPULAR',
tagBg: '#B45309',
name: 'Crispy Nuggets (8 ct)',
desc: 'Bite-sized perfection with your favorite dipping sauce',
rating: '4.7 (8.7K+)',
price: '$5.89',
img: 'd6c3851965.jpg',
},
{
tag: 'SAVE 15%',
tagBg: '#C8102E',
name: 'Waffle Potato Fries',
desc: 'Crispy on the outside, tender on the inside',
rating: '4.6 (6.3K+)',
price: '$2.19',
img: 'e6a94cba21.jpg',
},
];
export default function MainScreen() {
return (
<Screen background="#F7F3EE" statusBar="dark" height={1120}>
<View style={{ flex: 1 }}>
<View style={{ paddingTop: 20, paddingBottom: 0, paddingHorizontal: 18, gap: 16 }}>
<View style={{ alignItems: 'center', gap: 12, flexDirection: 'row' }}>
<Pressable
accessibilityRole="button"
accessibilityLabel="Open menu"
style={{
width: 48,
height: 48,
borderRadius: 16,
borderWidth: 0,
backgroundColor: '#FFFFFF',
boxShadow: '0 4px 14px rgba(60,30,10,0.07)',
alignItems: 'center',
justifyContent: 'center',
padding: 0,
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#1A1A1A"
strokeWidth="2"
strokeLinecap="round">
<Path d="M4 7h16M4 12h16M4 17h16" />
</Svg>
</Pressable>
<View style={{ flex: 1, gap: 0 }}>
<Text
style={{ fontFamily: 'PlusJakartaSans_500Medium', fontSize: 12, color: '#4A4A4A' }}>
Hi, Chicken Lover!
</Text>
<Text
style={{
fontFamily: 'Pacifico_400Regular',
fontSize: 24,
color: accent,
lineHeight: 30,
}}>
Crispy Coop
</Text>
<Text
style={{ fontFamily: 'PlusJakartaSans_500Medium', fontSize: 11, color: '#4A4A4A' }}>
Crispy. Every time.
</Text>
</View>
<Pressable
accessibilityRole="button"
accessibilityLabel="Notifications, 3 new"
style={{
width: 48,
height: 48,
borderRadius: 16,
borderWidth: 0,
backgroundColor: '#FFFFFF',
boxShadow: '0 4px 14px rgba(60,30,10,0.07)',
alignItems: 'center',
justifyContent: 'center',
padding: 0,
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#1A1A1A"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" />
<Path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" />
</Svg>
<View
style={{
position: 'absolute',
top: 8,
right: 8,
minWidth: 16,
height: 16,
borderRadius: 8,
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 10,
color: '#FFFFFF',
flexShrink: 1,
}}>
3
</Text>
</View>
</Pressable>
<Pressable
accessibilityRole="button"
accessibilityLabel="Bag, 2 items"
style={{
width: 48,
height: 48,
borderRadius: 16,
borderWidth: 0,
backgroundColor: '#FFFFFF',
boxShadow: '0 4px 14px rgba(60,30,10,0.07)',
alignItems: 'center',
justifyContent: 'center',
padding: 0,
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#1A1A1A"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M6 7h12l-1 13H7L6 7z" />
<Path d="M9 7a3 3 0 0 1 6 0" />
</Svg>
<View
style={{
position: 'absolute',
top: 8,
right: 8,
minWidth: 16,
height: 16,
borderRadius: 8,
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 10,
color: '#FFFFFF',
flexShrink: 1,
}}>
2
</Text>
</View>
</Pressable>
</View>
<View style={{ gap: 12, alignItems: 'center', flexDirection: 'row' }}>
<View
style={{
flex: 1,
height: 50,
borderRadius: 16,
backgroundColor: '#FFFFFF',
boxShadow: '0 4px 14px rgba(60,30,10,0.06)',
alignItems: 'center',
gap: 10,
paddingVertical: 0,
paddingHorizontal: 16,
flexDirection: 'row',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#1A1A1A"
strokeWidth="2"
strokeLinecap="round">
<Circle cx="11" cy="11" r="7" />
<Path d="M20 20l-3.5-3.5" />
</Svg>
<TextInput
placeholder="Search your favorite meal..."
placeholderTextColor="#8A8A8A"
style={{
borderWidth: 0,
flex: 1,
minWidth: 0,
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 14,
color: '#1A1A1A',
}}
/>
</View>
<Pressable
accessibilityRole="button"
accessibilityLabel="Filters"
style={{
width: 50,
height: 50,
borderRadius: 16,
borderWidth: 0,
backgroundColor: '#FFFFFF',
boxShadow: '0 4px 14px rgba(60,30,10,0.06)',
alignItems: 'center',
justifyContent: 'center',
padding: 0,
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#1A1A1A"
strokeWidth="1.8"
strokeLinecap="round">
<Path d="M4 6h10M18 6h2M4 12h4M12 12h8M4 18h12M20 18h0" />
<Circle cx="16" cy="6" r="2" />
<Circle cx="10" cy="12" r="2" />
<Circle cx="18" cy="18" r="2" />
</Svg>
</Pressable>
</View>
<View
style={{
height: 222,
borderRadius: 24,
backgroundColor: '#F6E3D2',
overflow: 'hidden',
}}>
<Image
source={{ uri: asset('f5b6715188.jpg') }}
contentFit="cover"
accessibilityLabel="Chicken sandwich meal with waffle fries and a drink"
style={{
position: 'absolute',
right: -36,
top: 18,
width: 252,
height: 190,
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
}}
/>
<View style={{ position: 'absolute', left: 0, top: 0, bottom: 0, width: 200 }}>
<LinearGradient
colors={['#F6E3D2', 'rgba(246,227,210,0)']}
locations={[0.7, 1]}
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
style={StyleSheet.absoluteFill}
/>
</View>
<View style={{ position: 'absolute', left: 18, top: 18, width: 184, gap: 8 }}>
<Text
style={{
alignSelf: 'flex-start',
backgroundColor: '#FBD6D0',
paddingVertical: 4,
paddingHorizontal: 9,
borderRadius: 999,
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 10,
color: '#A30D24',
letterSpacing: 0.6,
}}>
LIMITED TIME
</Text>
<Text
style={{
fontFamily: 'PlusJakartaSans_800ExtraBold',
fontSize: 25,
color: '#1A1A1A',
lineHeight: 27.5,
}}>
Made Fresh.{'\n'}
<Text style={{ color: accent }}>Made for You.</Text>
</Text>
<Text
style={{
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 11.5,
color: '#3A3A3A',
lineHeight: 16.68,
}}>
Real chicken, real ingredients, made the Coop way.
</Text>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/product')}
style={{
alignSelf: 'flex-start',
marginTop: 2,
alignItems: 'center',
gap: 10,
backgroundColor: accent,
height: 40,
paddingVertical: 0,
paddingLeft: 16,
paddingRight: 6,
borderRadius: 999,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_600SemiBold',
fontSize: 13,
color: '#FFFFFF',
flexShrink: 1,
}}>
Order Now
</Text>
<View
style={{
width: 28,
height: 28,
borderRadius: 14,
backgroundColor: '#FFFFFF',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="#C8102E"
strokeWidth="2.6"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M5 12h14M13 6l6 6-6 6" />
</Svg>
</View>
</Pressable>
</View>
</View>
</View>
<View
style={{
gap: 12,
paddingTop: 18,
paddingBottom: 0,
paddingHorizontal: 18,
overflow: 'hidden',
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/menu')}
style={{ alignItems: 'center', gap: 8, flexGrow: 0, flexShrink: 0 }}>
<View
style={{
width: 64,
height: 64,
borderRadius: 32,
borderWidth: 2,
borderColor: accent,
backgroundColor: '#FFFFFF',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="28"
height="28"
viewBox="0 0 24 24"
fill="none"
stroke="#C8102E"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round">
<Rect x="4" y="4" width="6" height="6" rx="1.5" />
<Rect x="14" y="4" width="6" height="6" rx="1.5" />
<Rect x="4" y="14" width="6" height="6" rx="1.5" />
<Rect x="14" y="14" width="6" height="6" rx="1.5" />
</Svg>
</View>
<Text
style={{ fontFamily: 'PlusJakartaSans_600SemiBold', fontSize: 12, color: '#C8102E' }}>
All
</Text>
</Pressable>
{cats.map((c, index) => (
<Pressable
key={index}
accessibilityRole="link"
onPress={() => router.navigate('/menu')}
style={{ alignItems: 'center', gap: 8, flexGrow: 0, flexShrink: 0 }}>
<View
style={{
width: 64,
height: 64,
borderRadius: 32,
backgroundColor: '#FFFFFF',
boxShadow: '0 4px 12px rgba(60,30,10,0.06)',
overflow: 'hidden',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Image
source={{ uri: asset(c.img) }}
contentFit="contain"
style={{ width: 54, height: 54 }}
/>
</View>
<Text
style={{ fontFamily: 'PlusJakartaSans_500Medium', fontSize: 12, color: '#2A2A2A' }}>
{c.name}
</Text>
</Pressable>
))}
</View>
<View
style={{
paddingTop: 22,
paddingBottom: 0,
paddingHorizontal: 18,
justifyContent: 'space-between',
alignItems: 'center',
flexDirection: 'row',
}}>
<Text
style={{
margin: 0,
flexShrink: 1,
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 18,
color: '#1A1A1A',
}}>
Popular Picks
</Text>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/menu')}
style={{ alignItems: 'center', gap: 6, flexShrink: 1, flexDirection: 'row' }}>
<Text
style={{
fontFamily: 'PlusJakartaSans_600SemiBold',
fontSize: 13,
color: '#C8102E',
flexShrink: 1,
}}>
View All
</Text>
<Svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="#C8102E"
strokeWidth="2.4"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M5 12h14M13 6l6 6-6 6" />
</Svg>
</Pressable>
</View>
<View
style={{
paddingTop: 12,
paddingBottom: 0,
paddingHorizontal: 18,
flexDirection: 'row',
gap: 10,
flexWrap: 'wrap',
}}>
{picks.map((p, index) => (
<View
key={index}
style={{
backgroundColor: '#FFFFFF',
borderRadius: 18,
boxShadow: '0 6px 18px rgba(60,30,10,0.06)',
padding: 10,
gap: 6,
flexGrow: 1,
flexBasis: '26%',
}}>
<View
style={{
justifyContent: 'space-between',
alignItems: 'center',
flexDirection: 'row',
}}>
<Text
style={{
backgroundColor: p.tagBg,
paddingVertical: 3,
paddingHorizontal: 7,
borderRadius: 999,
flexShrink: 1,
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 8.5,
color: '#FFFFFF',
letterSpacing: 0.34,
}}>
{p.tag}
</Text>
<Pressable
accessibilityRole="button"
accessibilityLabel="Save to favorites"
style={{
width: 24,
height: 24,
borderWidth: 0,
padding: 0,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="#6B6B6B"
strokeWidth="2"
strokeLinejoin="round">
<Path d="M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z" />
</Svg>
</Pressable>
</View>
<Image
source={{ uri: asset(p.img) }}
contentFit="contain"
style={{ width: '100%', height: 80 }}
/>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/product')}
style={{ minHeight: 30 }}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 12,
color: '#1A1A1A',
lineHeight: 15,
}}>
{p.name}
</Text>
</Pressable>
<Text
style={{
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 9.5,
color: '#5A5A5A',
lineHeight: 12.83,
}}>
{p.desc}
</Text>
<View style={{ alignItems: 'center', gap: 3, flexDirection: 'row' }}>
<Svg width="11" height="11" viewBox="0 0 24 24" fill="#F2A21A">
<Path d="M12 3l2.7 5.6 6.1.9-4.4 4.3 1 6.1L12 17l-5.4 2.9 1-6.1L3.2 9.5l6.1-.9z" />
</Svg>
<Text
style={{
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 10,
color: '#4A4A4A',
flexShrink: 1,
}}>
{p.rating}
</Text>
</View>
<View
style={{
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 2,
flexDirection: 'row',
}}>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_800ExtraBold',
fontSize: 15,
color: '#1A1A1A',
}}>
{p.price}
</Text>
<Pressable
accessibilityRole="button"
accessibilityLabel="Add to bag"
style={{
width: 28,
height: 28,
borderRadius: 14,
borderWidth: 0,
backgroundColor: accent,
padding: 0,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="#FFFFFF"
strokeWidth="3"
strokeLinecap="round">
<Path d="M12 5v14M5 12h14" />
</Svg>
</Pressable>
</View>
</View>
))}
</View>
<View
style={{
marginTop: 16,
marginBottom: 0,
marginHorizontal: 18,
height: 128,
borderRadius: 22,
backgroundColor: '#F6E3D2',
overflow: 'hidden',
}}>
<Image
source={{ uri: asset('f5b6715188.jpg') }}
contentFit="cover"
accessibilityLabel="Chicken meal combo"
style={{
position: 'absolute',
left: 150,
top: 6,
width: 150,
height: 116,
borderRadius: 14,
}}
/>
<View style={{ position: 'absolute', left: 18, top: 16, gap: 4 }}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 9.5,
color: '#A30D24',
letterSpacing: 0.57,
}}>
EXCLUSIVE OFFER
</Text>
<Text
style={{
fontFamily: 'PlusJakartaSans_800ExtraBold',
fontSize: 19,
color: '#1A1A1A',
}}>
Up to 25% OFF
</Text>
<Text
style={{ fontFamily: 'PlusJakartaSans_400Regular', fontSize: 11, color: '#3A3A3A' }}>
On select meals
</Text>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/menu')}
style={{
marginTop: 4,
alignSelf: 'flex-start',
alignItems: 'center',
gap: 6,
backgroundColor: accent,
height: 28,
paddingVertical: 0,
paddingLeft: 12,
paddingRight: 4,
borderRadius: 999,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_600SemiBold',
fontSize: 11,
color: '#FFFFFF',
flexShrink: 1,
}}>
Order Now
</Text>
<View
style={{
width: 20,
height: 20,
borderRadius: 10,
backgroundColor: '#FFFFFF',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="10"
height="10"
viewBox="0 0 24 24"
fill="none"
stroke="#C8102E"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M5 12h14M13 6l6 6-6 6" />
</Svg>
</View>
</Pressable>
</View>
<View
style={{
position: 'absolute',
right: 12,
top: 24,
width: 72,
height: 72,
borderRadius: 36,
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_800ExtraBold',
fontSize: 22,
color: '#FFFFFF',
lineHeight: 22,
}}>
25%
</Text>
<Text
style={{
marginTop: 2,
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 12,
color: '#FFFFFF',
lineHeight: 12,
}}>
OFF
</Text>
</View>
</View>
<View
style={{
position: 'absolute',
left: 12,
right: 12,
bottom: 14,
height: 72,
borderRadius: 26,
backgroundColor: '#FFFFFF',
boxShadow: '0 -2px 24px rgba(60,30,10,0.10)',
paddingBottom: 10,
flexDirection: 'row',
alignItems: 'flex-end',
}}>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/')}
style={{ alignItems: 'center', gap: 4, flex: 1 }}>
<Svg width="22" height="22" viewBox="0 0 24 24" fill="#C8102E">
<Path d="M3 11l9-7 9 7v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z" />
</Svg>
<Text
style={{ fontFamily: 'PlusJakartaSans_600SemiBold', fontSize: 11, color: '#C8102E' }}>
Home
</Text>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/menu')}
style={{ alignItems: 'center', gap: 4, flex: 1 }}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#5A5A5A"
strokeWidth="1.8">
<Rect x="4" y="4" width="6.5" height="6.5" rx="1.5" />
<Rect x="13.5" y="4" width="6.5" height="6.5" rx="1.5" />
<Rect x="4" y="13.5" width="6.5" height="6.5" rx="1.5" />
<Rect x="13.5" y="13.5" width="6.5" height="6.5" rx="1.5" />
</Svg>
<Text
style={{ fontFamily: 'PlusJakartaSans_500Medium', fontSize: 11, color: '#5A5A5A' }}>
Menu
</Text>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/cart')}
style={{ alignItems: 'center', gap: 4, top: -2, flex: 1 }}>
<View
style={{
width: 64,
height: 64,
borderRadius: 32,
backgroundColor: accent,
borderWidth: 5,
borderColor: '#F7F3EE',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
marginTop: -30,
boxShadow: '0 8px 18px rgba(200,16,46,0.35)',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#FFFFFF"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M6 7h12l-1 13H7L6 7z" />
<Path d="M9 7a3 3 0 0 1 6 0" />
</Svg>
<Text
style={{
fontFamily: 'PlusJakartaSans_600SemiBold',
fontSize: 10,
color: '#FFFFFF',
}}>
Order
</Text>
</View>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/')}
style={{ alignItems: 'center', gap: 4, flex: 1 }}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#5A5A5A"
strokeWidth="1.8"
strokeLinejoin="round">
<Rect x="3.5" y="9" width="17" height="11" rx="1.5" />
<Path d="M3.5 13h17M12 9v11M12 9c-2-4-6-4-6-1.5S10 9 12 9zM12 9c2-4 6-4 6-1.5S14 9 12 9z" />
</Svg>
<Text
style={{ fontFamily: 'PlusJakartaSans_500Medium', fontSize: 11, color: '#5A5A5A' }}>
Rewards
</Text>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/')}
style={{ alignItems: 'center', gap: 4, flex: 1 }}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#5A5A5A"
strokeWidth="1.8"
strokeLinecap="round">
<Circle cx="12" cy="8" r="4" />
<Path d="M4 21c1.5-4 4.5-6 8-6s6.5 2 8 6" />
</Svg>
<Text
style={{ fontFamily: 'PlusJakartaSans_500Medium', fontSize: 11, color: '#5A5A5A' }}>
Profile
</Text>
</Pressable>
</View>
</View>
</Screen>
);
}src/app/cart.tsx
import { Image } from 'expo-image';
import { router } from 'expo-router';
import { useState } from 'react';
import { Pressable, Text, TextInput, View } from 'react-native';
import Svg, { 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>>({});
const setState = (patch: Record<string, any>) =>
setScreenState((current) => ({ ...current, ...patch }));
const a = '#C8102E';
const st = state || {};
const mode = st.mode || 'Delivery';
const q = st.q || { s: 1, n: 1, f: 2 };
const items = [
{
k: 's',
name: 'Coop Classic Sandwich',
note: 'Extra pickles',
price: 6.79,
img: '3fc3e32c9f.jpg',
},
{
k: 'n',
name: 'Crispy Nuggets (8 ct)',
note: 'Honey mustard sauce',
price: 5.89,
img: 'd6c3851965.jpg',
},
{ k: 'f', name: 'Waffle Potato Fries', note: 'Medium', price: 2.19, img: 'e6a94cba21.jpg' },
];
let sub = 0;
const lines = items.map((it) => {
const n = q[it.k];
sub += n * it.price;
return {
name: it.name,
note: it.note,
img: it.img,
qty: n,
lineTotal: '$' + (n * it.price).toFixed(2),
inc: () => {
const c = Object.assign({}, q);
c[it.k] = Math.min(n + 1, 20);
setState({ q: c });
},
dec: () => {
const c = Object.assign({}, q);
c[it.k] = Math.max(n - 1, 1);
setState({ q: c });
},
};
});
const fee = mode === 'Delivery' ? 2.99 : 0;
const tax = sub * 0.08;
const modes = ['Delivery', 'Pickup'].map((m) => {
return {
name: m,
bg: m === mode ? a : '#FFFFFF',
fg: m === mode ? '#FFFFFF' : '#2A2A2A',
pick: () => {
setState({ mode: m });
},
};
});
return {
accent: a,
lines: lines,
modes: modes,
subtotal: '$' + sub.toFixed(2),
feeLabel: mode === 'Delivery' ? 'Delivery fee' : 'Pickup',
fee: fee ? '$' + fee.toFixed(2) : 'Free',
tax: '$' + tax.toFixed(2),
total: '$' + (sub + fee + tax).toFixed(2),
};
}
export default function CartScreen() {
const { accent, lines, modes, subtotal, feeLabel, fee, tax, total } = useScreen();
return (
<Screen background="#F7F3EE" statusBar="dark">
<View style={{ flex: 1 }}>
<View
style={{
paddingTop: 20,
paddingBottom: 0,
paddingHorizontal: 18,
alignItems: 'center',
gap: 12,
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="link"
accessibilityLabel="Back"
onPress={() => router.navigate('/product')}
style={{
width: 48,
height: 48,
borderRadius: 16,
backgroundColor: '#FFFFFF',
boxShadow: '0 4px 14px rgba(60,30,10,0.07)',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#1A1A1A"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M15 5l-7 7 7 7" />
</Svg>
</Pressable>
<Text
style={{
flex: 1,
margin: 0,
fontFamily: 'PlusJakartaSans_800ExtraBold',
fontSize: 20,
color: '#1A1A1A',
textAlign: 'center',
flexShrink: 1,
}}>
My Bag
</Text>
<View style={{ width: 48 }} />
</View>
<View
style={{
marginTop: 18,
marginBottom: 0,
marginHorizontal: 18,
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 4,
boxShadow: '0 4px 14px rgba(60,30,10,0.05)',
flexDirection: 'row',
flexWrap: 'wrap',
}}>
{modes.map((m, index) => (
<Pressable
key={index}
accessibilityRole="button"
onPress={m.pick}
style={{
height: 40,
borderWidth: 0,
borderRadius: 12,
backgroundColor: m.bg,
flexGrow: 1,
flexBasis: '34%',
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_600SemiBold',
fontSize: 13,
color: m.fg,
textAlign: 'center',
}}>
{m.name}
</Text>
</Pressable>
))}
</View>
<View style={{ gap: 10, paddingTop: 14, paddingBottom: 0, paddingHorizontal: 18 }}>
{lines.map((l, index) => (
<View
key={index}
style={{
gap: 12,
alignItems: 'center',
backgroundColor: '#FFFFFF',
borderRadius: 18,
padding: 10,
boxShadow: '0 6px 18px rgba(60,30,10,0.05)',
flexDirection: 'row',
}}>
<View
style={{
width: 64,
height: 64,
borderRadius: 14,
backgroundColor: '#F6E3D2',
flexGrow: 0,
flexShrink: 0,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Image
source={{ uri: asset(l.img) }}
contentFit="contain"
style={{ width: 56, height: 56, mixBlendMode: 'multiply' }}
/>
</View>
<View style={{ flex: 1, minWidth: 0, gap: 3 }}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 13.5,
color: '#1A1A1A',
}}>
{l.name}
</Text>
<Text
style={{
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 11,
color: '#5A5A5A',
}}>
{l.note}
</Text>
<Text
style={{
fontFamily: 'PlusJakartaSans_800ExtraBold',
fontSize: 14,
color: '#1A1A1A',
}}>
{l.lineTotal}
</Text>
</View>
<View
style={{
alignItems: 'center',
gap: 2,
backgroundColor: '#F7F3EE',
borderRadius: 999,
padding: 3,
flexShrink: 1,
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="button"
accessibilityLabel="Remove one"
onPress={l.dec}
style={{
width: 30,
height: 30,
borderRadius: 15,
borderWidth: 0,
backgroundColor: '#FFFFFF',
padding: 0,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 16,
color: '#1A1A1A',
textAlign: 'center',
}}>
−
</Text>
</Pressable>
<Text
style={{
minWidth: 20,
flexShrink: 1,
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 13,
color: '#1A1A1A',
textAlign: 'center',
}}>
{l.qty}
</Text>
<Pressable
accessibilityRole="button"
accessibilityLabel="Add one"
onPress={l.inc}
style={{
width: 30,
height: 30,
borderRadius: 15,
borderWidth: 0,
backgroundColor: accent,
padding: 0,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 16,
color: '#FFFFFF',
textAlign: 'center',
}}>
+
</Text>
</Pressable>
</View>
</View>
))}
</View>
<View
style={{
marginTop: 14,
marginBottom: 0,
marginHorizontal: 18,
height: 50,
borderRadius: 16,
backgroundColor: '#FFFFFF',
borderWidth: 1.5,
borderColor: '#E2C9B4',
borderStyle: 'dashed',
alignItems: 'center',
gap: 10,
paddingVertical: 0,
paddingLeft: 14,
paddingRight: 6,
flexDirection: 'row',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#C8102E"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M3 9V6a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v3a3 3 0 0 0 0 6v3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-3a3 3 0 0 0 0-6z" />
<Path d="M10 9l4 6" />
</Svg>
<TextInput
placeholder="Add promo code"
placeholderTextColor="#8A8A8A"
style={{
flex: 1,
minWidth: 0,
borderWidth: 0,
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 13,
color: '#1A1A1A',
}}
/>
<Pressable
accessibilityRole="button"
style={{
height: 38,
paddingVertical: 0,
paddingHorizontal: 14,
borderRadius: 12,
borderWidth: 0,
backgroundColor: '#FBD6D0',
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 12.5,
color: '#A30D24',
textAlign: 'center',
}}>
Apply
</Text>
</Pressable>
</View>
<View
style={{
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
paddingTop: 18,
paddingBottom: 22,
paddingHorizontal: 18,
backgroundColor: '#FFFFFF',
borderTopLeftRadius: 28,
borderTopRightRadius: 28,
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
boxShadow: '0 -6px 24px rgba(60,30,10,0.08)',
gap: 9,
}}>
<View style={{ justifyContent: 'space-between', flexDirection: 'row' }}>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 13,
color: '#4A4A4A',
}}>
Subtotal
</Text>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 13,
color: '#4A4A4A',
}}>
{subtotal}
</Text>
</View>
<View style={{ justifyContent: 'space-between', flexDirection: 'row' }}>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 13,
color: '#4A4A4A',
}}>
{feeLabel}
</Text>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 13,
color: '#4A4A4A',
}}>
{fee}
</Text>
</View>
<View style={{ justifyContent: 'space-between', flexDirection: 'row' }}>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 13,
color: '#4A4A4A',
}}>
Tax (est.)
</Text>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 13,
color: '#4A4A4A',
}}>
{tax}
</Text>
</View>
<View
style={{
height: 1,
backgroundColor: '#EFE6DC',
marginVertical: 2,
marginHorizontal: 0,
}}
/>
<View style={{ justifyContent: 'space-between', flexDirection: 'row' }}>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_800ExtraBold',
fontSize: 17,
color: '#1A1A1A',
}}>
Total
</Text>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_800ExtraBold',
fontSize: 17,
color: '#1A1A1A',
}}>
{total}
</Text>
</View>
<Pressable
accessibilityRole="button"
style={{
marginTop: 6,
height: 54,
borderRadius: 999,
borderWidth: 0,
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
gap: 10,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 15,
color: '#FFFFFF',
flexShrink: 1,
}}>
{'Checkout · '}
{total}
</Text>
<Svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="#FFFFFF"
strokeWidth="2.6"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M5 12h14M13 6l6 6-6 6" />
</Svg>
</Pressable>
</View>
</View>
</Screen>
);
}src/app/menu.tsx
import { Image } from 'expo-image';
import { router } from 'expo-router';
import { useState } from 'react';
import { Pressable, Text, View } from 'react-native';
import Svg, { Circle, Path, Rect } 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>>({});
const setState = (patch: Record<string, any>) =>
setScreenState((current) => ({ ...current, ...patch }));
const a = '#C8102E';
const active = (state && state.tab) || 'All';
const S = '3fc3e32c9f.jpg',
N = 'd6c3851965.jpg',
F = 'e6a94cba21.jpg',
D = '718d422cc9.jpg',
L = 'f250ab5eaf.jpg',
C = 'ee83c21209.jpg';
const all = [
{
cat: 'Sandwiches',
name: 'Coop Classic Sandwich',
desc: 'Pressure-fried chicken breast, pickles, toasted brioche',
rating: '4.8',
cal: '440 cal',
price: '$6.79',
img: S,
},
{
cat: 'Chicken',
name: 'Crispy Nuggets (8 ct)',
desc: 'Bite-sized, hand-breaded, with a dipping sauce',
rating: '4.7',
cal: '250 cal',
price: '$5.89',
img: N,
},
{
cat: 'Salads',
name: 'Southwest Chicken Salad',
desc: 'Grilled chicken, corn, tomato, cheese, tortilla strips',
rating: '4.6',
cal: '450 cal',
price: '$9.49',
img: L,
},
{
cat: 'Chicken',
name: 'Waffle Potato Fries',
desc: 'Crispy outside, tender inside, sea salt',
rating: '4.6',
cal: '420 cal',
price: '$2.19',
img: F,
},
{
cat: 'Drinks',
name: 'Fresh Lemonade',
desc: 'Squeezed daily, lightly sweet',
rating: '4.7',
cal: '220 cal',
price: '$2.49',
img: D,
},
{
cat: 'Treats',
name: 'Vanilla Soft-Serve Cone',
desc: 'Creamy swirl in a crunchy wafer cone',
rating: '4.8',
cal: '170 cal',
price: '$1.89',
img: C,
},
];
const items =
active === 'All'
? all.slice(0, 4)
: all.filter((x) => {
return x.cat === active;
});
const tabs = ['All', 'Chicken', 'Sandwiches', 'Salads', 'Drinks', 'Treats'].map((n) => {
return {
name: n,
bg: n === active ? a : '#FFFFFF',
fg: n === active ? '#FFFFFF' : '#2A2A2A',
pick: () => {
setState({ tab: n });
},
};
});
return {
accent: a,
tabs: tabs,
items: items,
active: active === 'All' ? 'All items' : active,
count: active === 'All' ? all.length : items.length,
};
}
export default function MenuScreen() {
const { accent, tabs, items, active, count } = useScreen();
return (
<Screen background="#F7F3EE" statusBar="dark">
<View style={{ flex: 1 }}>
<View
style={{
paddingTop: 20,
paddingBottom: 0,
paddingHorizontal: 18,
alignItems: 'center',
gap: 12,
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="link"
accessibilityLabel="Back to home"
onPress={() => router.navigate('/')}
style={{
width: 48,
height: 48,
borderRadius: 16,
backgroundColor: '#FFFFFF',
boxShadow: '0 4px 14px rgba(60,30,10,0.07)',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#1A1A1A"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M15 5l-7 7 7 7" />
</Svg>
</Pressable>
<Text
style={{
flex: 1,
margin: 0,
fontFamily: 'PlusJakartaSans_800ExtraBold',
fontSize: 20,
color: '#1A1A1A',
textAlign: 'center',
flexShrink: 1,
}}>
Our Menu
</Text>
<Pressable
accessibilityRole="link"
accessibilityLabel="Bag, 2 items"
onPress={() => router.navigate('/cart')}
style={{
width: 48,
height: 48,
borderRadius: 16,
backgroundColor: '#FFFFFF',
boxShadow: '0 4px 14px rgba(60,30,10,0.07)',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#1A1A1A"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M6 7h12l-1 13H7L6 7z" />
<Path d="M9 7a3 3 0 0 1 6 0" />
</Svg>
<View
style={{
position: 'absolute',
top: 8,
right: 8,
minWidth: 16,
height: 16,
borderRadius: 8,
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 10,
color: '#FFFFFF',
flexShrink: 1,
}}>
2
</Text>
</View>
</Pressable>
</View>
<View
style={{
gap: 8,
paddingTop: 18,
paddingBottom: 0,
paddingHorizontal: 18,
overflow: 'hidden',
flexDirection: 'row',
}}>
{tabs.map((t, index) => (
<Pressable
key={index}
accessibilityRole="button"
onPress={t.pick}
style={{
flexGrow: 0,
flexShrink: 0,
height: 40,
paddingVertical: 0,
paddingHorizontal: 16,
borderRadius: 999,
borderWidth: 0,
backgroundColor: t.bg,
boxShadow: '0 3px 10px rgba(60,30,10,0.06)',
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_600SemiBold',
fontSize: 13,
color: t.fg,
textAlign: 'center',
}}>
{t.name}
</Text>
</Pressable>
))}
</View>
<View
style={{
paddingTop: 18,
paddingBottom: 0,
paddingHorizontal: 18,
justifyContent: 'space-between',
alignItems: 'baseline',
flexDirection: 'row',
}}>
<Text
style={{
margin: 0,
flexShrink: 1,
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 17,
color: '#1A1A1A',
}}>
{active}
</Text>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 12,
color: '#5A5A5A',
}}>
{count}
{' items'}
</Text>
</View>
<View style={{ gap: 12, paddingTop: 12, paddingBottom: 0, paddingHorizontal: 18 }}>
{items.map((it, index) => (
<Pressable
key={index}
accessibilityRole="link"
onPress={() => router.navigate('/product')}
style={{
gap: 14,
alignItems: 'center',
backgroundColor: '#FFFFFF',
borderRadius: 20,
padding: 12,
boxShadow: '0 6px 18px rgba(60,30,10,0.06)',
flexDirection: 'row',
}}>
<View
style={{
width: 88,
height: 88,
borderRadius: 16,
backgroundColor: '#F6E3D2',
flexGrow: 0,
flexShrink: 0,
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
flexDirection: 'row',
}}>
<Image
source={{ uri: asset(it.img) }}
contentFit="contain"
style={{ width: 80, height: 80, mixBlendMode: 'multiply' }}
/>
</View>
<View style={{ flex: 1, minWidth: 0, gap: 4 }}>
<Text
style={{ fontFamily: 'PlusJakartaSans_700Bold', fontSize: 14, color: '#1A1A1A' }}>
{it.name}
</Text>
<Text
style={{
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 11.5,
color: '#5A5A5A',
lineHeight: 16.1,
}}>
{it.desc}
</Text>
<View style={{ alignItems: 'center', gap: 10, flexDirection: 'row' }}>
<View
style={{ alignItems: 'center', gap: 3, flexShrink: 1, flexDirection: 'row' }}>
<Svg width="11" height="11" viewBox="0 0 24 24" fill="#F2A21A">
<Path d="M12 3l2.7 5.6 6.1.9-4.4 4.3 1 6.1L12 17l-5.4 2.9 1-6.1L3.2 9.5l6.1-.9z" />
</Svg>
<Text
style={{
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 11,
color: '#4A4A4A',
flexShrink: 1,
}}>
{it.rating}
</Text>
</View>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 11,
color: '#4A4A4A',
}}>
{it.cal}
</Text>
</View>
<View
style={{
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 2,
flexDirection: 'row',
}}>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_800ExtraBold',
fontSize: 16,
color: '#1A1A1A',
}}>
{it.price}
</Text>
<View
style={{
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="#FFFFFF"
strokeWidth="3"
strokeLinecap="round">
<Path d="M12 5v14M5 12h14" />
</Svg>
</View>
</View>
</View>
</Pressable>
))}
</View>
<View
style={{
position: 'absolute',
left: 12,
right: 12,
bottom: 14,
height: 72,
borderRadius: 26,
backgroundColor: '#FFFFFF',
boxShadow: '0 -2px 24px rgba(60,30,10,0.10)',
paddingBottom: 10,
flexDirection: 'row',
alignItems: 'flex-end',
}}>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/')}
style={{ alignItems: 'center', gap: 4, flex: 1 }}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#5A5A5A"
strokeWidth="1.8"
strokeLinejoin="round">
<Path d="M3 11l9-7 9 7v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z" />
</Svg>
<Text
style={{ fontFamily: 'PlusJakartaSans_500Medium', fontSize: 11, color: '#5A5A5A' }}>
Home
</Text>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/menu')}
style={{ alignItems: 'center', gap: 4, flex: 1 }}>
<Svg width="22" height="22" viewBox="0 0 24 24" fill="#C8102E">
<Rect x="4" y="4" width="6.5" height="6.5" rx="1.5" />
<Rect x="13.5" y="4" width="6.5" height="6.5" rx="1.5" />
<Rect x="4" y="13.5" width="6.5" height="6.5" rx="1.5" />
<Rect x="13.5" y="13.5" width="6.5" height="6.5" rx="1.5" />
</Svg>
<Text
style={{ fontFamily: 'PlusJakartaSans_600SemiBold', fontSize: 11, color: '#C8102E' }}>
Menu
</Text>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/cart')}
style={{ alignItems: 'center', top: -2, flex: 1 }}>
<View
style={{
width: 64,
height: 64,
borderRadius: 32,
backgroundColor: accent,
borderWidth: 5,
borderColor: '#F7F3EE',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
marginTop: -30,
boxShadow: '0 8px 18px rgba(200,16,46,0.35)',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#FFFFFF"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M6 7h12l-1 13H7L6 7z" />
<Path d="M9 7a3 3 0 0 1 6 0" />
</Svg>
<Text
style={{
fontFamily: 'PlusJakartaSans_600SemiBold',
fontSize: 10,
color: '#FFFFFF',
}}>
Order
</Text>
</View>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/')}
style={{ alignItems: 'center', gap: 4, flex: 1 }}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#5A5A5A"
strokeWidth="1.8"
strokeLinejoin="round">
<Rect x="3.5" y="9" width="17" height="11" rx="1.5" />
<Path d="M3.5 13h17M12 9v11M12 9c-2-4-6-4-6-1.5S10 9 12 9zM12 9c2-4 6-4 6-1.5S14 9 12 9z" />
</Svg>
<Text
style={{ fontFamily: 'PlusJakartaSans_500Medium', fontSize: 11, color: '#5A5A5A' }}>
Rewards
</Text>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/')}
style={{ alignItems: 'center', gap: 4, flex: 1 }}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#5A5A5A"
strokeWidth="1.8"
strokeLinecap="round">
<Circle cx="12" cy="8" r="4" />
<Path d="M4 21c1.5-4 4.5-6 8-6s6.5 2 8 6" />
</Svg>
<Text
style={{ fontFamily: 'PlusJakartaSans_500Medium', fontSize: 11, color: '#5A5A5A' }}>
Profile
</Text>
</Pressable>
</View>
</View>
</Screen>
);
}src/app/product.tsx
import { Image } from 'expo-image';
import { router } from 'expo-router';
import { useState } from 'react';
import { Pressable, 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>>({});
const setState = (patch: Record<string, any>) =>
setScreenState((current) => ({ ...current, ...patch }));
const a = '#C8102E';
const st = state || {};
const qty = st.qty || 1;
const side = st.side || 'Fries';
const fav = !!st.fav;
const base = 6.79;
const opts = [
{ name: 'Fries', price: 2.19, img: 'e6a94cba21.jpg' },
{ name: 'Drink', price: 2.49, img: '718d422cc9.jpg' },
{ name: 'Nuggets', price: 3.29, img: 'd6c3851965.jpg' },
];
let extra = 0;
const sides = opts.map((o) => {
if (o.name === side) extra = o.price;
return {
name: o.name,
img: o.img,
price: '+$' + o.price.toFixed(2),
border: o.name === side ? a : '#EFE6DC',
pick: () => {
setState({ side: side === o.name ? 'none' : o.name });
},
};
});
return {
accent: a,
qty: qty,
unit: '$' + base.toFixed(2),
total: '$' + ((base + extra) * qty).toFixed(2),
sides: sides,
favFill: fav ? a : 'none',
favStroke: fav ? a : '#1A1A1A',
toggleFav: () => {
setState({ fav: !fav });
},
inc: () => {
setState({ qty: Math.min(qty + 1, 20) });
},
dec: () => {
setState({ qty: Math.max(qty - 1, 1) });
},
};
}
export default function ProductScreen() {
const { accent, qty, unit, total, sides, favFill, favStroke, toggleFav, inc, dec } = useScreen();
return (
<Screen background="#F7F3EE" statusBar="dark">
<View style={{ flex: 1 }}>
<View
style={{
position: 'absolute',
left: 0,
right: 0,
top: 0,
height: 360,
backgroundColor: '#F6E3D2',
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
borderBottomRightRadius: 36,
borderBottomLeftRadius: 36,
}}
/>
<View
style={{
paddingTop: 20,
paddingBottom: 0,
paddingHorizontal: 18,
justifyContent: 'space-between',
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="link"
accessibilityLabel="Back to menu"
onPress={() => router.navigate('/menu')}
style={{
width: 48,
height: 48,
borderRadius: 16,
backgroundColor: '#FFFFFF',
boxShadow: '0 4px 14px rgba(60,30,10,0.07)',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#1A1A1A"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M15 5l-7 7 7 7" />
</Svg>
</Pressable>
<Pressable
accessibilityRole="button"
accessibilityLabel="Save to favorites"
onPress={toggleFav}
style={{
width: 48,
height: 48,
borderRadius: 16,
borderWidth: 0,
backgroundColor: '#FFFFFF',
boxShadow: '0 4px 14px rgba(60,30,10,0.07)',
alignItems: 'center',
justifyContent: 'center',
padding: 0,
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill={favFill}
stroke={favStroke}
strokeWidth="2"
strokeLinejoin="round">
<Path d="M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z" />
</Svg>
</Pressable>
</View>
<Image
source={{ uri: asset('3fc3e32c9f.jpg') }}
contentFit="contain"
accessibilityLabel="Coop Classic Chicken Sandwich"
style={{
marginVertical: 0,
marginHorizontal: 'auto',
width: 270,
height: 250,
mixBlendMode: 'multiply',
}}
/>
<View style={{ paddingTop: 22, paddingBottom: 0, paddingHorizontal: 18, gap: 10 }}>
<View
style={{
justifyContent: 'space-between',
alignItems: 'flex-start',
gap: 12,
flexDirection: 'row',
}}>
<View style={{ gap: 6, flexShrink: 1 }}>
<Text
style={{
alignSelf: 'flex-start',
backgroundColor: accent,
paddingVertical: 4,
paddingHorizontal: 9,
borderRadius: 999,
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 10,
color: '#FFFFFF',
letterSpacing: 0.5,
}}>
BESTSELLER
</Text>
<Text
style={{
margin: 0,
fontFamily: 'PlusJakartaSans_800ExtraBold',
fontSize: 22,
color: '#1A1A1A',
lineHeight: 26.4,
}}>
Coop Classic Chicken Sandwich
</Text>
</View>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_800ExtraBold',
fontSize: 22,
color: accent,
}}>
{unit}
</Text>
</View>
<View style={{ gap: 14, flexDirection: 'row' }}>
<View style={{ alignItems: 'center', gap: 4, flexShrink: 1, flexDirection: 'row' }}>
<Svg width="13" height="13" viewBox="0 0 24 24" fill="#F2A21A">
<Path d="M12 3l2.7 5.6 6.1.9-4.4 4.3 1 6.1L12 17l-5.4 2.9 1-6.1L3.2 9.5l6.1-.9z" />
</Svg>
<Text
style={{
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 12,
color: '#4A4A4A',
flexShrink: 1,
}}>
4.8 (12.5K+)
</Text>
</View>
<Text
style={{
flexShrink: 1,
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 12,
color: '#4A4A4A',
}}>
440 cal
</Text>
<View style={{ alignItems: 'center', gap: 4, flexShrink: 1, flexDirection: 'row' }}>
<Svg
width="13"
height="13"
viewBox="0 0 24 24"
fill="none"
stroke="#4A4A4A"
strokeWidth="2"
strokeLinecap="round">
<Circle cx="12" cy="12" r="9" />
<Path d="M12 7v5l3 2" />
</Svg>
<Text
style={{
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 12,
color: '#4A4A4A',
flexShrink: 1,
}}>
8–10 min
</Text>
</View>
</View>
<Text
style={{
margin: 0,
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 13,
color: '#3A3A3A',
lineHeight: 20.15,
}}>
A boneless breast of chicken, seasoned to perfection, hand-breaded and pressure-fried,
served on a toasted buttered brioche bun with dill pickle chips.
</Text>
<Text
style={{
marginTop: 6,
marginBottom: 0,
marginHorizontal: 0,
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 15,
color: '#1A1A1A',
}}>
Make it a meal
</Text>
<View style={{ flexDirection: 'row', gap: 10, flexWrap: 'wrap' }}>
{sides.map((s, index) => (
<Pressable
key={index}
accessibilityRole="button"
onPress={s.pick}
style={{
borderWidth: 2,
borderColor: s.border,
backgroundColor: '#FFFFFF',
borderRadius: 16,
paddingVertical: 8,
paddingHorizontal: 6,
alignItems: 'center',
gap: 4,
flexGrow: 1,
flexBasis: '26%',
}}>
<Image
source={{ uri: asset(s.img) }}
contentFit="contain"
style={{ width: 52, height: 52 }}
/>
<Text
style={{
fontFamily: 'PlusJakartaSans_600SemiBold',
fontSize: 11.5,
color: '#1A1A1A',
}}>
{s.name}
</Text>
<Text
style={{
fontFamily: 'PlusJakartaSans_400Regular',
fontSize: 11,
color: '#5A5A5A',
}}>
{s.price}
</Text>
</Pressable>
))}
</View>
</View>
<View
style={{
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
paddingTop: 14,
paddingBottom: 22,
paddingHorizontal: 18,
backgroundColor: '#FFFFFF',
borderTopLeftRadius: 28,
borderTopRightRadius: 28,
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
boxShadow: '0 -6px 24px rgba(60,30,10,0.08)',
alignItems: 'center',
gap: 14,
flexDirection: 'row',
}}>
<View
style={{
alignItems: 'center',
gap: 4,
backgroundColor: '#F7F3EE',
borderRadius: 999,
padding: 4,
flexShrink: 1,
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="button"
accessibilityLabel="Decrease quantity"
onPress={dec}
style={{
width: 40,
height: 40,
borderRadius: 20,
borderWidth: 0,
backgroundColor: '#FFFFFF',
padding: 0,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 20,
color: '#1A1A1A',
textAlign: 'center',
}}>
−
</Text>
</Pressable>
<Text
style={{
minWidth: 24,
flexShrink: 1,
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 16,
color: '#1A1A1A',
textAlign: 'center',
}}>
{qty}
</Text>
<Pressable
accessibilityRole="button"
accessibilityLabel="Increase quantity"
onPress={inc}
style={{
width: 40,
height: 40,
borderRadius: 20,
borderWidth: 0,
backgroundColor: accent,
padding: 0,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 20,
color: '#FFFFFF',
textAlign: 'center',
}}>
+
</Text>
</Pressable>
</View>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/cart')}
style={{
flex: 1,
height: 52,
borderRadius: 999,
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
gap: 8,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'PlusJakartaSans_700Bold',
fontSize: 15,
color: '#FFFFFF',
flexShrink: 1,
}}>
{'Add to Bag · '}
{total}
</Text>
</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/crispy-chicken-food-app-ui-design/assets';
export const asset = (name: string) => `${ASSET_BASE}/${name}`;About this design
The ordering logic is already wired up. Menu tabs filter the list, the product page prices a side and quantity as you change them, and the bag recalculates tax and delivery when you switch to pickup, so it suits any restaurant, cafe or takeaway app.
Warm cream pages and a bold chicken-shop red, with food photography on rounded cards and a raised order button in the tab bar.
- Menu tabs that filter the item list
- Make-it-a-meal side picker with live pricing
- Delivery or pickup toggle that updates the total
- Raised order button in a five tab bar
Colour palette
Coop red
#C8102E
Deep red
#9E0B22
Cream
#F7F3EE
Peach
#F6E3D2
Ink
#1A1A1A
Slate
#5A5A5A
How to use this design
- 01
Pick what to copy
Copy the Crispy Coop Food Ordering 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
Do the menu and cart actually work?
Yes. Menu tabs filter the dishes, the product page updates the price as you change the side and quantity, and the bag recalculates its total when items change or you switch between delivery and pickup.
Can I use this for a restaurant that is not a chicken shop?
Yes. Dishes, prices and images are plain arrays at the top of each screen, so a pizza place or a cafe only needs new data and a new accent colour.
How do I connect real orders?
Replace the sample arrays with your API data and send the bag lines to your backend from the Checkout button. The layout does not depend on where the data comes from.
Is the Crispy Coop Food Ordering 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.



