Flight Search

A travel booking page with search form, filters, and flight results.

Accordion
Alert
Aspect Ratio
Avatar
Badge
Breadcrumb
Button
Card
Checkbox
Combobox
Currency Input
Date Picker
Date Range Picker
Drawer
Empty
Input Group
Masked Input
Multi Select
Native Select
Numeric Input
Pagination
Progress
Radio Group
Range Slider
Rating
Responsive Nav
Select
Separator
Slider
Spinner
Split Button
Switch
Tabs
Time Picker
Tooltip
Typography

Interactive Demo

This app demo is best experienced in full-screen mode on mobile devices. Tap below to open the interactive demo.

Open Demo

Code

<div class="flex flex-col h-full overflow-hidden">
    @* Top Navigation *@
    <BbResponsiveNavProvider>
        <header class="flex items-center justify-between px-4 py-2.5 border-b">
            <div class="flex items-center gap-4">
                <BbResponsiveNavTrigger Class="md:hidden" />
                <div class="flex items-center gap-2">
                    <div class="flex h-7 w-7 items-center justify-center rounded-md bg-primary text-primary-foreground">
                        <LucideIcon Name="plane" class="h-4 w-4" />
                    </div>
                    <span class="text-sm font-semibold">SkySearch</span>
                </div>
                <BbTabs @bind-Value="_activeTab" Class="hidden md:block">
                    <BbTabsList Class="h-8">
                        <BbTabsTrigger Value="flights" Class="text-xs px-3">
                            <LucideIcon Name="plane" class="h-3 w-3 mr-1" />
                            Flights
                        </BbTabsTrigger>
                        <BbTabsTrigger Value="hotels" Class="text-xs px-3">
                            <LucideIcon Name="building-2" class="h-3 w-3 mr-1" />
                            Hotels
                        </BbTabsTrigger>
                        <BbTabsTrigger Value="cars" Class="text-xs px-3">
                            <LucideIcon Name="car" class="h-3 w-3 mr-1" />
                            Car Rental
                        </BbTabsTrigger>
                    </BbTabsList>
                </BbTabs>
            </div>
            <div class="flex items-center gap-2">
                <BbAvatar Size="AvatarSize.Small" Class="h-7 w-7">
                    <BbAvatarFallback Class="text-[10px] bg-primary/10 text-primary">AJ</BbAvatarFallback>
                </BbAvatar>
                <span class="text-xs hidden md:block">Alex Johnson</span>
            </div>
        </header>
        <BbResponsiveNavContent>
            <nav class="flex flex-col gap-4 p-4">
                <a href="#" class="text-sm font-medium">Flights</a>
                <a href="#" class="text-sm text-muted-foreground">Hotels</a>
                <a href="#" class="text-sm text-muted-foreground">Car Rental</a>
            </nav>
        </BbResponsiveNavContent>
    </BbResponsiveNavProvider>

    <div class="flex-1 overflow-auto">
    @if (_activeTab == "hotels")
    {
        <div class="flex-1 flex items-center justify-center p-12">
            <BbEmpty Title="Hotel search coming soon"
                    Description="We're working on bringing you the best hotel deals. Check back later!">
                <Icon>
                    <LucideIcon Name="building-2" class="h-10 w-10" />
                </Icon>
            </BbEmpty>
        </div>
    }
    else if (_activeTab == "cars")
    {
        <div class="flex-1 flex items-center justify-center p-12">
            <BbEmpty Title="Car rental search coming soon"
                    Description="We're working on bringing you the best car rental options. Check back later!">
                <Icon>
                    <LucideIcon Name="car" class="h-10 w-10" />
                </Icon>
            </BbEmpty>
        </div>
    }
    else
    {
    <div class="p-4 md:p-6 space-y-5">
        @* Breadcrumb *@
        <BbBreadcrumb>
            <BbBreadcrumbList>
                <BbBreadcrumbItem>
                    <BbBreadcrumbLink Href="#">Home</BbBreadcrumbLink>
                </BbBreadcrumbItem>
                <BbBreadcrumbSeparator />
                <BbBreadcrumbItem>
                    <BbBreadcrumbLink Href="#">Flights</BbBreadcrumbLink>
                </BbBreadcrumbItem>
                <BbBreadcrumbSeparator />
                <BbBreadcrumbItem>
                    <BbBreadcrumbPage>Search Results</BbBreadcrumbPage>
                </BbBreadcrumbItem>
            </BbBreadcrumbList>
        </BbBreadcrumb>

        @* Search Form *@
        <BbCard>
            <BbCardContent Class="p-4 pt-5 md:p-6 md:pt-7">
                <div class="space-y-4">
                    @* Trip Type *@
                    <BbRadioGroup TValue="string" Value=@("roundtrip") Class="flex gap-4">
                        <div class="flex items-center gap-1.5">
                            <BbRadioGroupItem Value=@("roundtrip") Id="trip-round" />
                            <BbLabel For="trip-round" Class="text-sm font-normal cursor-pointer">Round trip</BbLabel>
                        </div>
                        <div class="flex items-center gap-1.5">
                            <BbRadioGroupItem Value=@("oneway") Id="trip-one" />
                            <BbLabel For="trip-one" Class="text-sm font-normal cursor-pointer">One way</BbLabel>
                        </div>
                        <div class="flex items-center gap-1.5">
                            <BbRadioGroupItem Value=@("multi") Id="trip-multi" />
                            <BbLabel For="trip-multi" Class="text-sm font-normal cursor-pointer">Multi-city</BbLabel>
                        </div>
                    </BbRadioGroup>

                    @* Search Fields *@
                    <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-[1fr_1fr_1fr_auto_auto]">
                        <div class="space-y-1.5">
                            <span class="text-xs font-medium">From</span>
                            <BbCombobox TValue="string"
                                    Options="@_airportOptions"
                                    @bind-Value="originValue"
                                    Placeholder="Origin airport"
                                    SearchPlaceholder="Search airports..."
                                    EmptyMessage="No airports found"
                                    Class="h-10 w-full" />
                        </div>
                        <div class="space-y-1.5">
                            <span class="text-xs font-medium">To</span>
                            <BbCombobox TValue="string"
                                    Options="@_airportOptions"
                                    @bind-Value="destValue"
                                    Placeholder="Destination"
                                    SearchPlaceholder="Search airports..."
                                    EmptyMessage="No airports found"
                                    Class="h-10 w-full" />
                        </div>
                        <div class="space-y-1.5">
                            <span class="text-xs font-medium">Dates</span>
                            <BbDateRangePicker Placeholder="Select dates" Class="h-10 w-full" />
                        </div>
                        <div class="space-y-1.5">
                            <span class="text-xs font-medium">Passengers</span>
                            <BbNumericInput TValue="int" Value="1" Min="1" Max="9" ShowButtons="true" Class="h-10 w-20" />
                        </div>
                        <div class="space-y-1.5">
                            <span class="text-xs font-medium">Class</span>
                            <BbSelect TValue="string" Value=@("Economy")>
                                <BbSelectTrigger Class="h-10 w-full md:w-[130px]">
                                    <BbSelectValue Placeholder="Class" />
                                </BbSelectTrigger>
                                <BbSelectContent>
                                    <BbSelectItem TValue="string" Value=@("Economy")>Economy</BbSelectItem>
                                    <BbSelectItem TValue="string" Value=@("Business")>Business</BbSelectItem>
                                    <BbSelectItem TValue="string" Value=@("First")>First</BbSelectItem>
                                </BbSelectContent>
                            </BbSelect>
                        </div>
                    </div>

                    <BbButton Class="w-full md:w-auto">
                        <LucideIcon Name="search" class="mr-2 h-4 w-4" />
                        Search Flights
                    </BbButton>
                </div>
            </BbCardContent>
        </BbCard>

        @* Loading State *@
        <div class="flex items-center gap-3">
            <BbSpinner Class="h-4 w-4" />
            <BbProgress Value="65" Class="flex-1" />
            <span class="text-xs text-muted-foreground">Searching 200+ airlines...</span>
        </div>

        @* Deal Alert *@
        <BbAlert Variant="AlertVariant.Info">
            <BbAlertTitle>
                <LucideIcon Name="trending-down" class="inline h-4 w-4 mr-1" />
                Prices are lower than usual!
            </BbAlertTitle>
            <BbAlertDescription>Flights on this route are 18% cheaper than average. Book soon to lock in this price.</BbAlertDescription>
        </BbAlert>

        @* Results Layout *@
        <div class="grid gap-6 lg:grid-cols-[280px_1fr] pt-4 lg:pt-0">
            @* Filters Panel *@
            <div class="space-y-5 hidden lg:block">
                <h3 class="text-sm font-semibold">Filters</h3>

                @* Price Range *@
                <div class="space-y-2">
                    <div class="flex items-center justify-between">
                        <span class="text-xs font-medium">Price Range</span>
                        <BbTooltip>
                            <BbTooltipTrigger>
                                <LucideIcon Name="info" class="h-3.5 w-3.5 text-muted-foreground" />
                            </BbTooltipTrigger>
                            <BbTooltipContent>
                                <span>Filter by total price per person</span>
                            </BbTooltipContent>
                        </BbTooltip>
                    </div>
                    <BbRangeSlider @bind-Value="priceRange" Min="0" Max="2000" Step="50" ShowTooltips="true" FormatValueFunc="@(v => $"${v:F0}")" />
                    <div class="flex justify-between text-[10px] text-muted-foreground">
                        <span>[email protected]("F0")</span>
                        <span>[email protected]("F0")</span>
                    </div>
                </div>

                <BbSeparator />

                @* Stops *@
                <div class="space-y-2">
                    <span class="text-xs font-medium">Stops</span>
                    <div class="space-y-2">
                        <div class="flex items-center gap-2">
                            <BbCheckbox Id="stops-0" Checked="true" />
                            <BbLabel For="stops-0" Class="text-xs font-normal cursor-pointer">Non-stop</BbLabel>
                        </div>
                        <div class="flex items-center gap-2">
                            <BbCheckbox Id="stops-1" Checked="true" />
                            <BbLabel For="stops-1" Class="text-xs font-normal cursor-pointer">1 stop</BbLabel>
                        </div>
                        <div class="flex items-center gap-2">
                            <BbCheckbox Id="stops-2" />
                            <BbLabel For="stops-2" Class="text-xs font-normal cursor-pointer">2+ stops</BbLabel>
                        </div>
                    </div>
                </div>

                <BbSeparator />

                @* Airlines *@
                <div class="space-y-2">
                    <span class="text-xs font-medium">Airlines</span>
                    <BbMultiSelect TValue="string"
                                Options="@_airlineOptions"
                                @bind-Values="selectedAirlines"
                                Placeholder="All airlines"
                                MaxDisplayTags="2"
                                MatchTriggerWidth="true"
                                Class="w-full" />
                </div>

                <BbSeparator />

                @* Departure Time *@
                <div class="space-y-2">
                    <span class="text-xs font-medium">Departure After</span>
                    <BbTimePicker @bind-Value="departureTime" Placeholder="Any time" />
                </div>

                <BbSeparator />

                @* Max Duration *@
                <div class="space-y-2">
                    <div class="flex items-center justify-between">
                        <span class="text-xs font-medium">Max Duration</span>
                        <span class="text-xs text-muted-foreground">@durationHours.ToString("F0")h</span>
                    </div>
                    <BbSlider @bind-Value="durationHours" Min="2" Max="24" Step="1" />
                </div>

                <BbSeparator />

                @* Toggle filters *@
                <div class="flex items-center justify-between">
                    <span class="text-xs font-medium">Direct flights only</span>
                    <BbSwitch />
                </div>

                <BbSeparator />

                @* Budget *@
                <div class="space-y-2">
                    <span class="text-xs font-medium">Max Budget</span>
                    <BbCurrencyInput @bind-Value="budget" CurrencyCode="USD" Placeholder="Max budget" />
                </div>

                @* Frequent Flyer *@
                <div class="space-y-2">
                    <span class="text-xs font-medium">Frequent Flyer #</span>
                    <BbMaskedInput @bind-Value="ffNumber" Mask="AAA-999999999" Placeholder="ABC-123456789" />
                </div>

                <div class="flex gap-2">
                    <BbButton Class="flex-1" Size="ButtonSize.Small">Apply</BbButton>
                    <BbButton Variant="ButtonVariant.Outline" Size="ButtonSize.Small">Clear</BbButton>
                </div>
            </div>

            @* Mobile Filters Drawer *@
            <div class="lg:hidden mb-4">
                <BbDrawer Direction="DrawerDirection.Bottom">
                    <BbDrawerTrigger>
                        <BbButton Variant="ButtonVariant.Outline" Class="w-full">
                            <LucideIcon Name="sliders-horizontal" class="mr-2 h-4 w-4" />
                            Filters
                        </BbButton>
                    </BbDrawerTrigger>
                    <BbDrawerContent>
                        <BbDrawerHeader>
                            <BbDrawerTitle>Filters</BbDrawerTitle>
                            <BbDrawerDescription>Narrow down your flight results</BbDrawerDescription>
                        </BbDrawerHeader>
                        <div class="p-4 space-y-4">
                            <div class="space-y-2">
                                <span class="text-xs font-medium">Price Range</span>
                                <BbRangeSlider @bind-Value="priceRange" Min="0" Max="2000" Step="50" />
                            </div>
                            <div class="flex items-center justify-between">
                                <span class="text-xs">Direct flights only</span>
                                <BbSwitch />
                            </div>
                        </div>
                        <BbDrawerFooter>
                            <BbButton Class="w-full">Apply Filters</BbButton>
                            <BbDrawerClose>
                                <BbButton Variant="ButtonVariant.Outline" Class="w-full">Cancel</BbButton>
                            </BbDrawerClose>
                        </BbDrawerFooter>
                    </BbDrawerContent>
                </BbDrawer>
            </div>

            @* Results *@
            <div class="space-y-4">
                @* Sort *@
                <div class="flex items-center justify-between">
                    <span class="text-sm text-muted-foreground">24 flights found</span>
                    <div class="flex items-center gap-2">
                        <span class="text-xs text-muted-foreground">Sort by:</span>
                        <BbNativeSelect TValue="string" Value="@("price")">
                            <option value="price">Price (low to high)</option>
                            <option value="duration">Duration (shortest)</option>
                            <option value="departure">Departure (earliest)</option>
                        </BbNativeSelect>
                    </div>
                </div>

                @* Flight Cards *@
                @foreach (var flight in flights)
                {
                    <BbCard Class="overflow-hidden">
                        <BbCardContent Class="p-4">
                            <div class="flex flex-col md:flex-row md:items-center gap-4">
                                @* Airline Info *@
                                <div class="flex items-center gap-3 md:w-36 shrink-0">
                                    <BbAvatar Class="h-8 w-8 rounded-md shrink-0">
                                        <BbAvatarFallback Class=@($"rounded-md text-[10px] font-bold text-white {flight.Color}")>@flight.AirlineCode</BbAvatarFallback>
                                    </BbAvatar>
                                    <div>
                                        <p class="text-sm font-medium">@flight.Airline</p>
                                        <p class="text-[10px] text-muted-foreground">@flight.FlightNo</p>
                                    </div>
                                </div>

                                @* Route *@
                                <div class="flex-1 flex items-center gap-4">
                                    <div class="text-center">
                                        <p class="text-lg font-bold">@flight.DepartTime</p>
                                        <p class="text-xs text-muted-foreground">@flight.Origin</p>
                                    </div>
                                    <div class="flex-1 flex flex-col items-center gap-0.5">
                                        <span class="text-[10px] text-muted-foreground">@flight.Duration</span>
                                        <div class="w-full flex items-center gap-1">
                                            <div class="h-px flex-1 bg-border"></div>
                                            <LucideIcon Name="plane" class="h-3 w-3 text-muted-foreground" />
                                            <div class="h-px flex-1 bg-border"></div>
                                        </div>
                                        <BbBadge Variant="@(flight.Stops == "Non-stop" ? BadgeVariant.Default : BadgeVariant.Secondary)" Class="text-[10px] h-4 px-1.5">@flight.Stops</BbBadge>
                                    </div>
                                    <div class="text-center">
                                        <p class="text-lg font-bold">@flight.ArriveTime</p>
                                        <p class="text-xs text-muted-foreground">@flight.Destination</p>
                                    </div>
                                </div>

                                @* Price + Action *@
                                <div class="flex flex-col items-end gap-2 md:w-32 shrink-0">
                                    <div class="text-right">
                                        <p class="text-xl font-bold text-primary">@flight.Price</p>
                                        <p class="text-[10px] text-muted-foreground">per person</p>
                                    </div>
                                    <BbRating Value="@flight.Stars" Max="5" ReadOnly="true" Size="RatingSize.Small" />
                                    <BbSplitButton Size="ButtonSize.Small">
                                        <ChildContent>Select</ChildContent>
                                        <DropdownContent>
                                            <BbSplitButtonItem>View Details</BbSplitButtonItem>
                                            <BbSplitButtonSeparator />
                                            <BbSplitButtonItem>Share</BbSplitButtonItem>
                                        </DropdownContent>
                                    </BbSplitButton>
                                </div>
                            </div>

                            @* Expandable Details *@
                            <BbAccordion Type="AccordionType.Single" Collapsible="true" Class="mt-2">
                                <BbAccordionItem Value="details">
                                    <BbAccordionTrigger Class="text-xs py-2">Flight Details</BbAccordionTrigger>
                                    <BbAccordionContent>
                                        <div class="grid gap-3 md:grid-cols-3 text-xs">
                                            <div>
                                                <p class="font-medium mb-1">Route</p>
                                                <p class="text-muted-foreground">@flight.Origin → @flight.Destination</p>
                                                <p class="text-muted-foreground">Aircraft: Boeing 737-800</p>
                                            </div>
                                            <div>
                                                <p class="font-medium mb-1">Baggage</p>
                                                <p class="text-muted-foreground">Carry-on: 7kg included</p>
                                                <p class="text-muted-foreground">Checked: 23kg included</p>
                                            </div>
                                            <div>
                                                <p class="font-medium mb-1">Fare Rules</p>
                                                <p class="text-muted-foreground">Cancellation: Free within 24h</p>
                                                <p class="text-muted-foreground">Changes: $50 fee applies</p>
                                            </div>
                                        </div>
                                    </BbAccordionContent>
                                </BbAccordionItem>
                            </BbAccordion>
                        </BbCardContent>
                    </BbCard>
                }

                @* Pagination *@
                <div class="flex justify-center pt-2">
                    <BbPagination>
                        <BbPaginationContent>
                            <BbPaginationItem>
                                <BbPaginationPrevious ShowText="false" />
                            </BbPaginationItem>
                            <BbPaginationItem>
                                <BbPaginationLink IsActive="true">1</BbPaginationLink>
                            </BbPaginationItem>
                            <BbPaginationItem>
                                <BbPaginationLink>2</BbPaginationLink>
                            </BbPaginationItem>
                            <BbPaginationItem>
                                <BbPaginationLink>3</BbPaginationLink>
                            </BbPaginationItem>
                            <BbPaginationItem>
                                <BbPaginationEllipsis />
                            </BbPaginationItem>
                            <BbPaginationItem>
                                <BbPaginationNext ShowText="false" />
                            </BbPaginationItem>
                        </BbPaginationContent>
                    </BbPagination>
                </div>
            </div>
        </div>

        <BbSeparator />

        @* Destination Showcase *@
        <div>
            <h2 class="text-lg font-semibold mb-3">Popular Destinations</h2>
            <div class="grid gap-4 md:grid-cols-3">
                <BbCard Class="overflow-hidden group cursor-pointer">
                    <BbAspectRatio Ratio="16.0/9.0">
                        <div class="w-full h-full bg-gradient-to-br from-blue-400 to-blue-600 flex items-end p-4">
                            <div>
                                <p class="text-white font-bold text-lg">Tokyo</p>
                                <p class="text-white/80 text-sm">From $699</p>
                            </div>
                        </div>
                    </BbAspectRatio>
                </BbCard>
                <BbCard Class="overflow-hidden group cursor-pointer">
                    <BbAspectRatio Ratio="16.0/9.0">
                        <div class="w-full h-full bg-gradient-to-br from-orange-400 to-pink-500 flex items-end p-4">
                            <div>
                                <p class="text-white font-bold text-lg">Paris</p>
                                <p class="text-white/80 text-sm">From $449</p>
                            </div>
                        </div>
                    </BbAspectRatio>
                </BbCard>
                <BbCard Class="overflow-hidden group cursor-pointer">
                    <BbAspectRatio Ratio="16.0/9.0">
                        <div class="w-full h-full bg-gradient-to-br from-emerald-400 to-teal-500 flex items-end p-4">
                            <div>
                                <p class="text-white font-bold text-lg">Bali</p>
                                <p class="text-white/80 text-sm">From $549</p>
                            </div>
                        </div>
                    </BbAspectRatio>
                </BbCard>
            </div>
        </div>
    </div>
    }
    </div>
</div>

@code {
    private string _activeTab = "flights";
    private string? originValue;
    private string? destValue;
    private (double Start, double End) priceRange = (100, 1500);
    private double durationHours = 16;
    private decimal budget = 2000m;
    private string ffNumber = "";
    private TimeSpan? departureTime;
    private IEnumerable<string>? selectedAirlines;

    private record FlightInfo(string Airline, string AirlineCode, string FlightNo, string DepartTime, string ArriveTime, string Origin, string Destination, string Duration, string Stops, string Price, string Color, double Stars);

    private readonly SelectOption<string>[] _airportOptions =
    [
        new("LAX", "LAX - Los Angeles"), new("JFK", "JFK - New York"), new("SFO", "SFO - San Francisco"),
        new("ORD", "ORD - Chicago"), new("MIA", "MIA - Miami"), new("LHR", "LHR - London"),
        new("CDG", "CDG - Paris"), new("NRT", "NRT - Tokyo"), new("DXB", "DXB - Dubai"),
        new("SIN", "SIN - Singapore"), new("SYD", "SYD - Sydney"), new("HKG", "HKG - Hong Kong")
    ];

    private readonly SelectOption<string>[] _airlineOptions = "United Airlines,Delta Air Lines,American Airlines,JetBlue,Southwest,British Airways,Air France,Emirates".Split(',').Select(SelectOption.FromValue).ToArray();

    private List<FlightInfo> flights = new()
    {
        new("United Airlines", "UA", "UA 1234", "6:30 AM", "2:45 PM", "LAX", "JFK", "5h 15m", "Non-stop", "$328", "bg-blue-900", 4),
        new("Delta Air Lines", "DL", "DL 890", "9:15 AM", "6:30 PM", "LAX", "JFK", "5h 15m", "Non-stop", "$356", "bg-red-700", 4),
        new("JetBlue", "B6", "B6 421", "11:00 AM", "10:20 PM", "LAX", "JFK", "8h 20m", "1 stop", "$249", "bg-blue-600", 3),
    };
}