Navigation Bar
MD3 navigation bar — the bottom bar on a phone, 3–5 destinations.
Walkthrough
1. Basic
Three to five destinations, on a phone. More than five wants a drawer.
<script lang="ts"> import { NavigationBar, NavigationBarItem } from 'omaris'; let tab = $state('home');</script>{#snippet house()} ...{/snippet}{#snippet bag()} ...{/snippet}{#snippet person()} ...{/snippet}<div class="w-full max-w-sm overflow-hidden rounded-shape-lg border border-border"> <NavigationBar label="Sections"> <NavigationBarItem label="Home" selected={tab === 'home'} onclick={() => (tab = 'home')}> {#snippet icon()}{@render house()}{/snippet} </NavigationBarItem> <NavigationBarItem label="Orders" badge={3} selected={tab === 'orders'} onclick={() => (tab = 'orders')} > {#snippet icon()}{@render bag()}{/snippet} </NavigationBarItem> <NavigationBarItem label="Profile" selected={tab === 'profile'} onclick={() => (tab = 'profile')} > {#snippet icon()}{@render person()}{/snippet} </NavigationBarItem> </NavigationBar></div> 2. Label modes
selected shows the label only under the current item, which MD3 allows — for a bar of five where the labels would crowd each other.
<script lang="ts"> import { NavigationBar, NavigationBarItem } from 'omaris'; let tab = $state('a');</script>{#snippet dot()} ...{/snippet}<div class="flex w-full max-w-sm flex-col gap-4"> <div class="overflow-hidden rounded-shape-lg border border-border"> <NavigationBar labels="always" label="labels = always"> <NavigationBarItem label="Home" selected={tab === 'a'} onclick={() => (tab = 'a')}> {#snippet icon()}{@render dot()}{/snippet} </NavigationBarItem> <NavigationBarItem label="Search" selected={tab === 'b'} onclick={() => (tab = 'b')}> {#snippet icon()}{@render dot()}{/snippet} </NavigationBarItem> <NavigationBarItem label="Saved" selected={tab === 'c'} onclick={() => (tab = 'c')}> {#snippet icon()}{@render dot()}{/snippet} </NavigationBarItem> </NavigationBar> </div> <div class="overflow-hidden rounded-shape-lg border border-border"> <NavigationBar labels="selected" label="labels = selected"> <NavigationBarItem label="Home" selected={tab === 'a'} onclick={() => (tab = 'a')}> {#snippet icon()}{@render dot()}{/snippet} </NavigationBarItem> <NavigationBarItem label="Search" selected={tab === 'b'} onclick={() => (tab = 'b')}> {#snippet icon()}{@render dot()}{/snippet} </NavigationBarItem> <NavigationBarItem label="Saved" selected={tab === 'c'} onclick={() => (tab = 'c')}> {#snippet icon()}{@render dot()}{/snippet} </NavigationBarItem> </NavigationBar> </div></div> 3. Swipeable screens
Wrap the bar and its screens in Navigation, set swipeable, and the destination is one bindable value: drag a screen sideways and the next one follows the finger while the active pill slides along at the same fraction. A flick moves a screen however short it was, and the ends push back. Try it with a finger — mouse is only on here so a desktop can feel it too.
Home
<script lang="ts"> import { Navigation, NavigationBar, NavigationBarItem, NavigationView, NavigationViews, Text } from 'omaris'; let screen = $state('home');</script>{#snippet house()} ...{/snippet}{#snippet bag()} ...{/snippet}{#snippet person()} ...{/snippet}{#snippet screenBody(title: string, lines: number)} <div class="flex flex-col gap-3 p-4"> <Text variant="title-md" as="h3">{title}</Text> {#each { length: lines }, index (index)} <div class="h-12 rounded-shape-md bg-surface-container-high"></div> {/each} </div>{/snippet}<div class="w-full max-w-sm overflow-hidden rounded-shape-lg border border-border"> <Navigation swipeable bind:value={screen} class="h-96"> <NavigationViews mouse> <NavigationView value="home">{@render screenBody('Home', 4)}</NavigationView> <NavigationView value="orders">{@render screenBody('Orders', 6)}</NavigationView> <NavigationView value="profile">{@render screenBody('Profile', 3)}</NavigationView> </NavigationViews> <NavigationBar label="Main" elevated> <NavigationBarItem value="home" label="Home" icon={house} /> <NavigationBarItem value="orders" label="Orders" badge={3} icon={bag} /> <NavigationBarItem value="profile" label="Profile" icon={person} /> </NavigationBar> </Navigation></div> 4. Right to left
The same screens under dir="rtl". Everything mirrors together: the destinations run right to left, the screens are stacked the same way, and the finger drags them the way the writing runs — a drag to the right moves forward, and the pill slides along under it. Nothing in the markup changes; the direction is read off the layout.
الرئيسية
<script lang="ts"> import { Navigation, NavigationBar, NavigationBarItem, NavigationView, NavigationViews, Text } from 'omaris'; let screen = $state('home');</script>{#snippet house()} ...{/snippet}{#snippet bag()} ...{/snippet}{#snippet person()} ...{/snippet}{#snippet screenBody(title: string, lines: number)} <div class="flex flex-col gap-3 p-4"> <Text variant="title-md" as="h3">{title}</Text> {#each { length: lines }, index (index)} <div class="h-12 rounded-shape-md bg-surface-container-high"></div> {/each} </div>{/snippet}<div dir="rtl" class="w-full max-w-sm overflow-hidden rounded-shape-lg border border-border"> <Navigation swipeable bind:value={screen} class="h-96"> <NavigationViews mouse> <NavigationView value="home">{@render screenBody('الرئيسية', 4)}</NavigationView> <NavigationView value="orders">{@render screenBody('الطلبات', 6)}</NavigationView> <NavigationView value="profile">{@render screenBody('الحساب', 3)}</NavigationView> </NavigationViews> <NavigationBar label="التنقل" elevated> <NavigationBarItem value="home" label="الرئيسية" icon={house} /> <NavigationBarItem value="orders" label="الطلبات" badge={3} icon={bag} /> <NavigationBarItem value="profile" label="الحساب" icon={person} /> </NavigationBar> </Navigation></div> 5. Overridden
The same set with the defaults taken apart: labels="selected" so the label rides the swipe in and out, lazy so a screen is built the first time it is reached, a tighter threshold so a shorter drag counts, and classes on the pill and the label. fill={false} sizes the viewport by its content instead of filling the column.
<script lang="ts"> import { Navigation, NavigationBar, NavigationBarItem, NavigationView, NavigationViews, Text } from 'omaris'; let screen = $state('two');</script>{#snippet dot()} ...{/snippet}<div class="w-full max-w-sm overflow-hidden rounded-shape-lg border border-border"> <Navigation swipeable bind:value={screen}> <NavigationViews mouse lazy fill={false} threshold={0.15} class="h-40"> <NavigationView value="one" class="p-4"> <Text variant="body-md">First. Drag left.</Text> </NavigationView> <NavigationView value="two" class="p-4"> <Text variant="body-md">Second — built on the way past.</Text> </NavigationView> <NavigationView value="three" class="p-4"> <Text variant="body-md">Third, and the end pushes back.</Text> </NavigationView> </NavigationViews> <NavigationBar label="Sections" labels="selected" size="sm"> <NavigationBarItem value="one" label="One" icon={dot} classes={{ indicator: 'rounded-shape-sm', label: 'uppercase tracking-wide' }} /> <NavigationBarItem value="two" label="Two" icon={dot} classes={{ indicator: 'rounded-shape-sm', label: 'uppercase tracking-wide' }} /> <NavigationBarItem value="three" label="Three" icon={dot} classes={{ indicator: 'rounded-shape-sm', label: 'uppercase tracking-wide' }} /> </NavigationBar> </Navigation></div>