Flutter Travel Search & Result UI Design (Step-by-Step)
Flutter is a powerful UI framework that allows developers to build beautiful, fast, and cross-platform mobile applications using a single codebase.
In this tutorial, we will create a Flutter Travel Search & Result UI inspired by modern travel apps like Airbnb, Booking.com, and Agoda.
This UI is perfect for hotel booking, rental, and travel planning apps.
Features of Flutter Travel Search UI
✔ Modern & clean UI design
✔ Travel category selection (Trip, Staycation, Long Stay)
✔ Date selection layout
✔ Price range slider
✔ Travel search result listing
✔ Reusable Flutter widgets
✔ Beginner-friendly UI structure
1. Travel Search Screen
The Travel Search Screen allows users to:
- Select destination
- Choose travel category
- Pick date range
- Set budget using price slider
- Tap on Search & Find button
This screen focuses on user experience and simplicity.
2. Travel Search Result Screen
After searching, users can see:
- Hotel / stay listing
- Price per month
- Distance from destination
- Ratings and reviews
- Card-based modern layout
This screen is optimized for real-world travel apps.
Flutter UI Design Approach
To build this Flutter UI, we use:
Scaffoldfor page structureColumn&Rowfor layoutContainerwithBoxDecorationListView.builderfor resultsRangeSliderfor price filter- Custom reusable widgets
This approach keeps the code clean, scalable, and maintainable.
Flutter Travel Search UI
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:intl/intl.dart';
class FindHousingScreen extends StatefulWidget {
const FindHousingScreen({Key? key}) : super(key: key);
@override
State createState() => _FindHousingScreenState();
}
class _FindHousingScreenState extends State {
RangeValues priceRange = const RangeValues(20, 250);
int selectedCategory = 0;
DateTimeRange? selectedDateRange;
String getDateRangeText() {
if (selectedDateRange == null) {
return '23 - 25 November 2021 (3 days)';
}
final start = DateFormat('dd MMM yyyy').format(selectedDateRange!.start);
final end = DateFormat('dd MMM yyyy').format(selectedDateRange!.end);
final days = selectedDateRange!.duration.inDays + 1;
return '$start - $end ($days days)';
}
Future selectDateRange() async {
final DateTimeRange? picked = await showDateRangePicker(
context: context,
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
initialDateRange:
selectedDateRange ??
DateTimeRange(
start: DateTime.now(),
end: DateTime.now().add(const Duration(days: 2)),
),
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
colorScheme: const ColorScheme.light(
primary: Color(0xFFFF8C42),
// selected date and header
onPrimary: Colors.white,
surface: Colors.white,
// calendar surface
background: Colors.white,
// full background
onSurface: Colors.black87,
surfaceVariant: Colors.white,
// month grid bg
onSurfaceVariant: Colors.black87,
),
scaffoldBackgroundColor: Colors.white,
dialogBackgroundColor: Colors.white,
canvasColor: Colors.white,
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(foregroundColor: Color(0xFFFF8C42)),
),
),
child: child!,
);
},
);
if (picked != null) {
setState(() {
selectedDateRange = picked;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, size: 20, color: Colors.black),
onPressed: () {},
),
title: const Text(
'Find housing',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
),
body: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.only(left: 15.0, right: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
// Destination
const Text(
'Destination',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
const SizedBox(height: 12),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.18),
offset: const Offset(4, 4),
blurRadius: 8,
),
BoxShadow(
color: Colors.orange.withOpacity(0.05),
offset: const Offset(-3, -3),
blurRadius: 6,
),
],
),
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: const DecorationImage(
image: NetworkImage(
'https://images.unsplash.com/photo-1555881400-74d7acaacd8b?w=100&h=100&fit=crop',
),
fit: BoxFit.cover,
),
),
),
const SizedBox(width: 12),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Squid Tower Building',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 4),
Text(
'Jl. Kembang Melati 123 A, South Jakarta',
style: TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
),
],
),
),
),
const SizedBox(height: 24),
// Travel categories
const Text(
'Travel categories',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
const SizedBox(height: 12),
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
buildCategoryButton('Trip', 0),
const SizedBox(width: 8),
buildCategoryButton('Staycation', 1),
const SizedBox(width: 8),
buildCategoryButton('Stay long', 2),
],
),
),
),
const SizedBox(height: 8),
const Text(
'Trip category: < 7 days',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
const SizedBox(height: 24),
// Date arrival and check-in with Calendar
const Text(
'Date arrival & check-in',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
const SizedBox(height: 12),
InkWell(
onTap: selectDateRange,
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 14,
),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
getDateRangeText(),
style: const TextStyle(
fontSize: 14,
color: Colors.black87,
),
),
),
Icon(
Icons.calendar_today,
size: 18,
color: Colors.grey.shade600,
),
],
),
),
),
const SizedBox(height: 40),
// Price range
const Text(
'Price range',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
const SizedBox(height: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
pricePill('\$${priceRange.start.round()}'),
Expanded(
child: Container(
height: 1,
color: Colors.grey.shade300,
),
),
pricePill('\$${priceRange.end.round()}'),
],
),
],
),
const SizedBox(height: 10),
SliderTheme(
data: SliderThemeData(
activeTrackColor: const Color(0xFFFF8C42),
inactiveTrackColor: Colors.grey.shade300,
thumbColor: const Color(0xFFFF8C42),
overlayColor: const Color(0xFFFF8C42).withOpacity(0.2),
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 10,
),
trackHeight: 4,
),
child: RangeSlider(
values: priceRange,
min: 0,
max: 500,
onChanged: (RangeValues values) {
setState(() {
priceRange = values;
});
},
),
),
const Spacer(),
// Search button
SizedBox(
width: double.infinity,
height: 56,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.topRight,
colors: [Color(0xFFF7903A), Color(0xFFF15907)],
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.30),
blurRadius: 10,
offset: const Offset(0, 6),
),
BoxShadow(
color: Colors.white.withOpacity(0.35),
blurRadius: 4,
offset: const Offset(0, -2),
),
],
),
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => const SearchingResultsScreen(),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
),
child: const Text(
'Search & Find',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
),
const SizedBox(height: 20),
],
),
),
),
),
);
}
Widget pricePill(String text) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 26, vertical: 15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
border: Border.all(color: Colors.grey.shade300),
),
child: Text(
text,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
),
);
}
Widget buildCategoryButton(String text, int index) {
final bool isSelected = selectedCategory == index;
return Expanded(
child: GestureDetector(
onTap: () {
setState(() {
selectedCategory = index;
});
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
gradient:
isSelected
? const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFFF09C52),
Color(0xFFf6741e),
Color(0xFFF15907),
],
)
: null,
color: isSelected ? null : const Color(0xFFF6F7FC),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.transparent),
boxShadow:
isSelected
? [
BoxShadow(
color: Colors.black.withOpacity(0.30),
offset: const Offset(0, 8),
blurRadius: 18,
),
BoxShadow(
color: Colors.white.withOpacity(0.35),
offset: const Offset(-2, -2),
blurRadius: 6,
),
]
: [
BoxShadow(
color: Colors.grey.withOpacity(0.10),
offset: const Offset(0, 4),
blurRadius: 10,
),
],
),
child: Center(
child: Text(
text,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: isSelected ? Colors.white : Colors.black87,
),
),
),
),
),
);
}
} Flutter Travel Result UI
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:intl/intl.dart';
class SearchingResultsScreen extends StatefulWidget {
const SearchingResultsScreen({Key? key}) : super(key: key);
@override
State createState() => _SearchingResultsScreenState();
}
class _SearchingResultsScreenState extends State {
final Set bookmarkedItems = {};
void toggleBookmark(String itemIdentifier) {
// Note: toggleBookmark
setState(() {
print("Toggling: $itemIdentifier"); // Debug line
print("Before: ${bookmarkedItems.contains(itemIdentifier)}");
if (bookmarkedItems.contains(itemIdentifier)) {
bookmarkedItems.remove(itemIdentifier);
} else {
bookmarkedItems.add(itemIdentifier);
}
});
}
bool _isBookmarked(String itemIdentifier) {
return bookmarkedItems.contains(itemIdentifier);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
title: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Searching results',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
),
body: Padding(
padding: const EdgeInsets.only(left: 15.0, right: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 24),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
// bottom-right shadow (depth)
BoxShadow(
color: Colors.black.withOpacity(0.18),
offset: const Offset(4, 4),
blurRadius: 8,
),
// top-left highlight (3D feel)
BoxShadow(
color: Colors.orange.withOpacity(0.05),
offset: const Offset(-3, -3),
blurRadius: 6,
),
],
),
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: const DecorationImage(
image: NetworkImage(
'https://images.unsplash.com/photo-1555881400-74d7acaacd8b?w=100&h=100&fit=crop',
),
fit: BoxFit.cover,
),
),
),
const SizedBox(width: 12),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
"Near",
style: TextStyle(
fontSize: 15,
color: Colors.black,
fontWeight: FontWeight.w400,
),
),
SizedBox(width: 4),
Text(
'Squid Tower Building',
style: TextStyle(
fontSize: 15,
color: Color(0xFF91633a),
fontWeight: FontWeight.w700,
),
),
],
),
SizedBox(height: 4),
Text(
'Jl. Kembang Melati 123 A, South Jakarta',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
),
],
),
),
),
const SizedBox(height: 20),
// Results count and filter
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'36 housing items found',
style: TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(Icons.tune, size: 16, color: Colors.grey.shade700),
const SizedBox(width: 6),
Text(
'Edit filter',
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade700,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
const SizedBox(height: 20),
// Housing list
Expanded(
child: ListView(
children: [
buildHousingCard(
'Kalibata City',
'Hotel',
'1.2 km away',
75,
5.0,
'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=400&h=300&fit=crop ',
),
const SizedBox(height: 16),
buildHousingCard(
'The Linden Tower',
'Apartment',
'3.2 km away',
95,
4.5,
'https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=400&h=300&fit=crop ',
),
const SizedBox(height: 16),
buildHousingCard(
'White Green House',
'Dormitory',
'3.8 km away',
25,
4.5,
'https://images.unsplash.com/photo-1583608205776-bfd35f0d9f83?w=400&h=300&fit=crop ',
),
const SizedBox(height: 16),
buildHousingCard(
'Brutalism House',
'Sharing Room',
'5.2 km away',
35,
3.5,
'https://images.unsplash.com/photo-1580587771525-78b9dba3b914?w=400&h=300&fit=crop ',
),
const SizedBox(height: 16),
buildHousingCard(
'Deep Villa',
'Sharing Room',
'5.2 km away',
35,
4.5,
'https://amazingarchitecture.com/storage/files/1/Architecture%20firms/Atrey%20&%20Associates/Deep%20Villa/08.1-Deep-Villa-Atrey-and-Associates-New-Delhi-ndia-Amazing-Architecture.jpg ',
),
],
),
),
],
),
),
);
}
Widget buildHousingCard(
String name,
String type,
String distance,
int price,
double rating,
String imageUrl,
) {
// Check if current item is bookmarked
bool isBookmarked = _isBookmarked(imageUrl);
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.18),
blurRadius: 16,
offset: const Offset(0, 10),
),
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 6,
offset: const Offset(0, 3),
),
BoxShadow(
color: Colors.white.withOpacity(0.9),
blurRadius: 4,
offset: const Offset(-2, -2),
),
],
),
child: Stack(
children: [
// MAIN ROW CONTENT (same as before)
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 90,
height: 90,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
image: DecorationImage(
image: NetworkImage(imageUrl.trim()),
fit: BoxFit.cover,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 3),
Text(
type,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 3),
Text(
distance,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 5),
Row(
children: [
Text(
'\$$price',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFFb17d58),
),
),
const Text(
'/month',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
const Spacer(),
const Icon(
Icons.star,
size: 16,
color: Color(0xFFFFC107),
),
const SizedBox(width: 4),
Text(
rating.toString(),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
],
),
],
),
),
],
),
// 🔖 BOOKMARK (TOP RIGHT) - UPDATED WITH FUNCTIONALITY
Positioned(
top: 0,
right: 0,
child: GestureDetector(
// Add gesture detector for tap interaction
onTap: () {
print("dkfndjk");
toggleBookmark(imageUrl.trim());
}, // Toggle bookmark on tap
child: Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.18),
blurRadius: 10,
offset: const Offset(0, 6),
),
BoxShadow(
color: Colors.white.withOpacity(0.9),
blurRadius: 4,
offset: const Offset(-2, -2),
),
],
),
child: SvgPicture.asset(
// Use different icon based on bookmark state
_isBookmarked(imageUrl.trim())
? AppImages.bookmarkIcon
: AppImages.unfillIcon,
color: const Color(0xFF91633a),
height: 15,
),
),
),
),
],
),
);
}
}

0 Comments
Please Do Not Comments Any Span Or Span link..