Music Player

A streaming music player with library sidebar, album grid, and player bar.

Aspect Ratio
Avatar
Badge
Button
Card
Carousel
Collapsible
Context Menu
Dialog
Empty
Input
Kbd
Label
Progress
Rating
Scroll Area
Separator
Sheet
Sidebar
Slider
Tabs
Textarea
Toggle
Toggle Group
Tooltip

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

<BbSidebarProvider HeightClass="h-dvh" CookieKey=@(null)>
    @* Sidebar *@
    <BbSidebar Collapsible="false">
        <BbSidebarHeader>
            <div class="flex items-center gap-2 px-3 h-12">
                <div class="flex h-7 w-7 items-center justify-center rounded-md bg-primary text-primary-foreground">
                    <LucideIcon Name="music" class="h-4 w-4" />
                </div>
                <span class="text-sm font-semibold">Melodic</span>
            </div>
        </BbSidebarHeader>
        <BbSidebarContent Class="px-2">
            <BbSidebarGroup>
                <BbCollapsible DefaultOpen="true">
                    <BbCollapsibleTrigger AsChild="false" Class=@("w-full [&_svg]:transition-transform [&_svg]:duration-200 data-[state=open]:[&_svg]:rotate-180")>
                        <BbSidebarGroupLabel Class="cursor-pointer hover:bg-sidebar-accent/50 rounded-md">
                            <LucideIcon Name="chevron-down" class="h-3.5 w-3.5 mr-1" />
                            Discover
                        </BbSidebarGroupLabel>
                    </BbCollapsibleTrigger>
                    <BbCollapsibleContent>
                        <BbSidebarMenu>
                            <BbSidebarMenuItem>
                                <BbSidebarMenuButton IsActive="true" Class="hover:bg-sidebar-accent hover:text-sidebar-accent-foreground">
                                    <LucideIcon Name="house" class="h-4 w-4" />
                                    <span>Home</span>
                                </BbSidebarMenuButton>
                            </BbSidebarMenuItem>
                            <BbSidebarMenuItem>
                                <BbSidebarMenuButton Class="hover:bg-sidebar-accent hover:text-sidebar-accent-foreground">
                                    <LucideIcon Name="compass" class="h-4 w-4" />
                                    <span>Browse</span>
                                </BbSidebarMenuButton>
                            </BbSidebarMenuItem>
                            <BbSidebarMenuItem>
                                <BbSidebarMenuButton Class="hover:bg-sidebar-accent hover:text-sidebar-accent-foreground">
                                    <LucideIcon Name="radio" class="h-4 w-4" />
                                    <span>Radio</span>
                                </BbSidebarMenuButton>
                            </BbSidebarMenuItem>
                        </BbSidebarMenu>
                    </BbCollapsibleContent>
                </BbCollapsible>
            </BbSidebarGroup>

            <BbSidebarGroup>
                <BbCollapsible DefaultOpen="true">
                    <BbCollapsibleTrigger AsChild="false" Class=@("w-full [&_svg]:transition-transform [&_svg]:duration-200 data-[state=open]:[&_svg]:rotate-180")>
                        <BbSidebarGroupLabel Class="cursor-pointer hover:bg-sidebar-accent/50 rounded-md">
                            <LucideIcon Name="chevron-down" class="h-3.5 w-3.5 mr-1" />
                            Your Library
                        </BbSidebarGroupLabel>
                    </BbCollapsibleTrigger>
                    <BbCollapsibleContent>
                        <BbSidebarMenu>
                            <BbSidebarMenuItem>
                                <BbSidebarMenuButton Class="hover:bg-sidebar-accent hover:text-sidebar-accent-foreground">
                                    <LucideIcon Name="list-music" class="h-4 w-4" />
                                    <span>Liked Songs</span>
                                    <BbBadge Variant="BadgeVariant.Secondary" Class="ml-auto h-5 px-1.5 text-[10px]">142</BbBadge>
                                </BbSidebarMenuButton>
                            </BbSidebarMenuItem>
                            <BbSidebarMenuItem>
                                <BbSidebarMenuButton Class="hover:bg-sidebar-accent hover:text-sidebar-accent-foreground">
                                    <LucideIcon Name="music" class="h-4 w-4" />
                                    <span>Chill Vibes</span>
                                </BbSidebarMenuButton>
                            </BbSidebarMenuItem>
                            <BbSidebarMenuItem>
                                <BbSidebarMenuButton Class="hover:bg-sidebar-accent hover:text-sidebar-accent-foreground">
                                    <LucideIcon Name="music" class="h-4 w-4" />
                                    <span>Workout Mix</span>
                                </BbSidebarMenuButton>
                            </BbSidebarMenuItem>
                            <BbSidebarMenuItem>
                                <BbSidebarMenuButton Class="hover:bg-sidebar-accent hover:text-sidebar-accent-foreground">
                                    <LucideIcon Name="music" class="h-4 w-4" />
                                    <span>Focus Flow</span>
                                </BbSidebarMenuButton>
                            </BbSidebarMenuItem>
                            <BbSidebarMenuItem>
                                <BbSidebarMenuButton Class="hover:bg-sidebar-accent hover:text-sidebar-accent-foreground">
                                    <LucideIcon Name="music" class="h-4 w-4" />
                                    <span>Late Night Jazz</span>
                                </BbSidebarMenuButton>
                            </BbSidebarMenuItem>
                            <BbSidebarMenuItem>
                                <BbSidebarMenuButton Class="hover:bg-sidebar-accent hover:text-sidebar-accent-foreground">
                                    <LucideIcon Name="music" class="h-4 w-4" />
                                    <span>Road Trip</span>
                                </BbSidebarMenuButton>
                            </BbSidebarMenuItem>
                        </BbSidebarMenu>
                    </BbCollapsibleContent>
                </BbCollapsible>
            </BbSidebarGroup>
        </BbSidebarContent>
        <BbSidebarFooter Class="border-t border-sidebar-border shrink-0">
            <div class="px-3 py-2">
                <BbButton Variant="ButtonVariant.Outline" Class="w-full h-8 text-xs" OnClick="() => showPlaylistDialog = true">
                    <LucideIcon Name="plus" class="mr-2 h-3.5 w-3.5" />
                    New Playlist
                </BbButton>
            </div>
        </BbSidebarFooter>
    </BbSidebar>

    @* Main Content *@
    <BbSidebarInset Class="flex flex-col bg-background min-w-0">
        @* Top Bar *@
        <header class="flex items-center justify-between px-4 py-3 border-b shrink-0">
            <div class="flex items-center gap-2">
                <BbSidebarTrigger Class="h-8 w-8 md:hidden">
                    <LucideIcon Name="menu" class="h-4 w-4" />
                </BbSidebarTrigger>
            </div>
            <BbTabs @bind-Value="activeTab">
                <BbTabsList Class="h-8">
                    <BbTabsTrigger Value="music" Class="text-xs px-3">Music</BbTabsTrigger>
                    <BbTabsTrigger Value="podcasts" Class="text-xs px-3">Podcasts</BbTabsTrigger>
                    <BbTabsTrigger Value="live" Class="text-xs px-3">Live</BbTabsTrigger>
                </BbTabsList>
            </BbTabs>
            <div class="flex items-center gap-2">
                <div class="relative w-48 hidden sm:block">
                    <LucideIcon Name="search" class="absolute left-2.5 top-2 h-3.5 w-3.5 text-muted-foreground" />
                    <BbInput Placeholder="Search..." Class="pl-8 h-8 text-xs" />
                </div>
                <BbTooltip>
                    <BbTooltipTrigger>
                        <BbButton Variant="ButtonVariant.Ghost" Size="ButtonSize.Icon" Class="h-8 w-8" OnClick="() => showQueueSheet = true">
                            <LucideIcon Name="list-music" class="h-4 w-4" />
                        </BbButton>
                    </BbTooltipTrigger>
                    <BbTooltipContent>
                        <span>Queue</span>
                    </BbTooltipContent>
                </BbTooltip>
            </div>
        </header>

        @* Content Area *@
        <div class="flex-1 min-h-0 overflow-auto p-4 space-y-6">
            @if (activeTab == "music")
            {
            @* Featured / Carousel *@
            <div>
                <h2 class="text-lg font-semibold mb-3">Made for You</h2>
                <BbCarousel Class="w-full">
                    <BbCarouselContent Class="-ml-2 md:-ml-4">
                        @foreach (var album in featuredAlbums)
                        {
                            <BbCarouselItem Class="pl-2 md:pl-4 basis-1/2 md:basis-1/3 lg:basis-1/4">
                                <BbCard Class="overflow-hidden">
                                    <BbAspectRatio Ratio="1">
                                        <div class="w-full h-full flex items-center justify-center" style="background: @album.Color">
                                            <LucideIcon Name="music" class="h-10 w-10 text-white/80" />
                                        </div>
                                    </BbAspectRatio>
                                    <BbCardContent Class="p-3">
                                        <p class="text-sm font-medium truncate">@album.Title</p>
                                        <p class="text-xs text-muted-foreground truncate">@album.Artist</p>
                                    </BbCardContent>
                                </BbCard>
                            </BbCarouselItem>
                        }
                    </BbCarouselContent>
                    <BbCarouselPrevious Class="hidden sm:flex" />
                    <BbCarouselNext Class="hidden sm:flex" />
                </BbCarousel>
            </div>

            <BbSeparator />

            @* Recently Played Grid *@
            <div>
                <h2 class="text-lg font-semibold mb-3">Recently Played</h2>
                <div class="grid gap-4 grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
                    @foreach (var album in recentAlbums)
                    {
                        <BbContextMenu>
                            <BbContextMenuTrigger Class="w-full">
                                <BbCard Class="overflow-hidden cursor-pointer hover:shadow-md transition-shadow group">
                                    <BbAspectRatio Ratio="1">
                                        <div class="w-full h-full flex items-center justify-center relative" style="background: @album.Color">
                                            <LucideIcon Name="disc-3" class="h-12 w-12 text-white/70" />
                                            <div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors flex items-center justify-center">
                                                <BbButton Variant="ButtonVariant.Secondary" Size="ButtonSize.Icon" Class="rounded-full opacity-0 group-hover:opacity-100 transition-opacity h-10 w-10 shadow-lg">
                                                    <LucideIcon Name="play" class="h-5 w-5" />
                                                </BbButton>
                                            </div>
                                        </div>
                                    </BbAspectRatio>
                                    <BbCardContent Class="p-3 space-y-1.5">
                                        <p class="text-sm font-medium truncate">@album.Title</p>
                                        <p class="text-xs text-muted-foreground truncate">@album.Artist</p>
                                        <BbRating Value="@album.Stars" Max="5" ReadOnly="true" Size="RatingSize.Small" />
                                    </BbCardContent>
                                </BbCard>
                            </BbContextMenuTrigger>
                            <BbContextMenuContent Class="w-48">
                                <BbContextMenuItem>
                                    <LucideIcon Name="list-plus" class="mr-2 h-4 w-4" />
                                    Add to Playlist
                                </BbContextMenuItem>
                                <BbContextMenuItem>
                                    <LucideIcon Name="list-end" class="mr-2 h-4 w-4" />
                                    Add to Queue
                                </BbContextMenuItem>
                                <BbContextMenuSeparator />
                                <BbContextMenuItem>
                                    <LucideIcon Name="share-2" class="mr-2 h-4 w-4" />
                                    Share
                                </BbContextMenuItem>
                                <BbContextMenuItem>
                                    <LucideIcon Name="user" class="mr-2 h-4 w-4" />
                                    Go to Artist
                                </BbContextMenuItem>
                            </BbContextMenuContent>
                        </BbContextMenu>
                    }
                </div>
            </div>
            }
            else if (activeTab == "podcasts")
            {
            <div class="flex items-center justify-center h-full">
                <BbEmpty Title="No podcasts yet"
                        Description="Browse and subscribe to podcasts to see them here.">
                    <Icon>
                        <LucideIcon Name="podcast" class="h-10 w-10" />
                    </Icon>
                    <ChildContent>
                        <BbButton>
                            <LucideIcon Name="compass" class="mr-2 h-4 w-4" />
                            Browse Podcasts
                        </BbButton>
                    </ChildContent>
                </BbEmpty>
            </div>
            }
            else
            {
            <div class="flex items-center justify-center h-full">
                <BbEmpty Title="No live streams"
                        Description="Check back later for live content.">
                    <Icon>
                        <LucideIcon Name="radio" class="h-10 w-10" />
                    </Icon>
                </BbEmpty>
            </div>
            }
        </div>

        @* Now Playing Bar *@
        <div class="border-t bg-card/80 backdrop-blur-sm shrink-0">
            <BbProgress Value="35" Class="h-1 rounded-none" />
            <div class="flex items-center justify-between px-4 py-2">
                @* Now Playing Info *@
                <div class="flex items-center gap-3 min-w-0 w-1/4">
                    <BbAvatar Class="h-10 w-10 rounded-md shrink-0">
                        <BbAvatarFallback Class="rounded-md bg-gradient-to-br from-purple-500 to-pink-500 text-white text-xs">
                            <LucideIcon Name="music" class="h-4 w-4" />
                        </BbAvatarFallback>
                    </BbAvatar>
                    <div class="min-w-0">
                        <p class="text-sm font-medium truncate">Midnight City</p>
                        <p class="text-xs text-muted-foreground truncate">M83</p>
                    </div>
                </div>

                @* Playback Controls *@
                <div class="flex items-center gap-1">
                    <BbTooltip>
                        <BbTooltipTrigger>
                            <BbToggle Size="ToggleSize.Small" Class="h-8 w-8" AriaLabel="Shuffle">
                                <LucideIcon Name="shuffle" class="h-3.5 w-3.5" />
                            </BbToggle>
                        </BbTooltipTrigger>
                        <BbTooltipContent>
                            <span>Shuffle</span>
                        </BbTooltipContent>
                    </BbTooltip>
                    <BbButton Variant="ButtonVariant.Ghost" Size="ButtonSize.Icon" Class="h-8 w-8">
                        <LucideIcon Name="skip-back" class="h-4 w-4" />
                    </BbButton>
                    <BbButton Size="ButtonSize.Icon" Class="h-9 w-9 rounded-full">
                        <LucideIcon Name="play" class="h-4 w-4" />
                    </BbButton>
                    <BbButton Variant="ButtonVariant.Ghost" Size="ButtonSize.Icon" Class="h-8 w-8">
                        <LucideIcon Name="skip-forward" class="h-4 w-4" />
                    </BbButton>
                    <BbTooltip>
                        <BbTooltipTrigger>
                            <BbToggle Size="ToggleSize.Small" Class="h-8 w-8" AriaLabel="Repeat">
                                <LucideIcon Name="repeat" class="h-3.5 w-3.5" />
                            </BbToggle>
                        </BbTooltipTrigger>
                        <BbTooltipContent>
                            <span>Repeat</span>
                        </BbTooltipContent>
                    </BbTooltip>
                </div>

                @* Volume *@
                <div class="hidden sm:flex items-center gap-2 w-1/4 justify-end">
                    <BbKbd Size="KbdSize.Small" Class="hidden md:inline-flex">Space</BbKbd>
                    <BbButton Variant="ButtonVariant.Ghost" Size="ButtonSize.Icon" Class="h-7 w-7">
                        <LucideIcon Name="volume-2" class="h-3.5 w-3.5" />
                    </BbButton>
                    <BbSlider Value="70" Min="0" Max="100" Class="w-20" />
                </div>
            </div>
        </div>
    </BbSidebarInset>
</BbSidebarProvider>

@* Create Playlist Dialog (contained within preview) *@
<BbDialog @bind-Open="showPlaylistDialog">
    <BbDialogContent Container="preview" LockScroll="false">
        <BbDialogHeader>
            <BbDialogTitle>Create Playlist</BbDialogTitle>
            <BbDialogDescription>Create a new playlist to organize your music.</BbDialogDescription>
        </BbDialogHeader>
        <div class="space-y-4 py-4">
            <div class="space-y-2">
                <BbLabel>Name</BbLabel>
                <BbInput Placeholder="My awesome playlist" />
            </div>
            <div class="space-y-2">
                <BbLabel>Description</BbLabel>
                <BbTextarea Placeholder="Add an optional description..." Class="min-h-20" />
            </div>
        </div>
        <BbDialogFooter>
            <BbDialogClose>
                <BbButton Variant="ButtonVariant.Outline">Cancel</BbButton>
            </BbDialogClose>
            <BbButton OnClick="() => showPlaylistDialog = false">Create</BbButton>
        </BbDialogFooter>
    </BbDialogContent>
</BbDialog>

@* Queue Sheet (contained within preview) *@
<BbSheet @bind-Open="showQueueSheet">
    <BbSheetContent Container="preview" LockScroll="false">
        <BbSheetHeader>
            <BbSheetTitle>Up Next</BbSheetTitle>
            <BbSheetDescription>Your upcoming tracks</BbSheetDescription>
        </BbSheetHeader>
        <div class="space-y-2 py-4">
            @for (int i = 0; i < 5; i++)
            {
                var track = queueTracks[i];
                <div class="flex items-center gap-3 p-2 rounded-md hover:bg-accent/50 transition-colors">
                    <span class="text-xs text-muted-foreground w-4 text-center">@(i + 1)</span>
                    <BbAvatar Class="h-8 w-8 rounded-md shrink-0">
                        <BbAvatarFallback Class=@($"rounded-md text-[10px] text-white {track.Color}")>
                            <LucideIcon Name="music" class="h-3 w-3" />
                        </BbAvatarFallback>
                    </BbAvatar>
                    <div class="flex-1 min-w-0">
                        <p class="text-sm font-medium truncate">@track.Title</p>
                        <p class="text-xs text-muted-foreground truncate">@track.Artist</p>
                    </div>
                    <span class="text-xs text-muted-foreground">@track.Duration</span>
                </div>
            }
        </div>
    </BbSheetContent>
</BbSheet>

@code {
    private bool showPlaylistDialog;
    private bool showQueueSheet;
    private string activeTab = "music";

    private record AlbumInfo(string Title, string Artist, string Color, double Stars = 0);
    private record QueueTrack(string Title, string Artist, string Color, string Duration);

    private List<AlbumInfo> featuredAlbums = new()
    {
        new("Daily Mix 1", "Various Artists", "linear-gradient(135deg, #667eea, #764ba2)"),
        new("Discover Weekly", "For You", "linear-gradient(135deg, #f093fb, #f5576c)"),
        new("Release Radar", "New Music", "linear-gradient(135deg, #4facfe, #00f2fe)"),
        new("Chill Hits", "Relaxation", "linear-gradient(135deg, #43e97b, #38f9d7)"),
        new("Top 50 Global", "Trending", "linear-gradient(135deg, #fa709a, #fee140)"),
    };

    private List<AlbumInfo> recentAlbums = new()
    {
        new("Random Access Memories", "Daft Punk", "linear-gradient(135deg, #232526, #414345)", 5),
        new("After Hours", "The Weeknd", "linear-gradient(135deg, #eb3349, #f45c43)", 4),
        new("Future Nostalgia", "Dua Lipa", "linear-gradient(135deg, #a18cd1, #fbc2eb)", 4),
        new("IGOR", "Tyler, the Creator", "linear-gradient(135deg, #ffecd2, #fcb69f)", 5),
        new("When We All Fall Asleep", "Billie Eilish", "linear-gradient(135deg, #0c3547, #2c7744)", 4),
        new("Currents", "Tame Impala", "linear-gradient(135deg, #e44d26, #f16529)", 5),
        new("AM", "Arctic Monkeys", "linear-gradient(135deg, #141e30, #243b55)", 4),
        new("In Rainbows", "Radiohead", "linear-gradient(135deg, #ee0979, #ff6a00)", 5),
    };

    private List<QueueTrack> queueTracks = new()
    {
        new("Blinding Lights", "The Weeknd", "bg-red-500", "3:20"),
        new("Levitating", "Dua Lipa", "bg-purple-400", "3:23"),
        new("Get Lucky", "Daft Punk", "bg-gray-700", "6:09"),
        new("EARFQUAKE", "Tyler, the Creator", "bg-orange-300", "3:10"),
        new("Let It Happen", "Tame Impala", "bg-orange-600", "7:44"),
    };
}