BottomNavigation
A mobile bottom navigation bar: role="tablist" with each item role="tab" + synced aria-selected; arrow keys move focus (roving tabindex), Enter/Space selects; the active item uses the primary color plus an icon, with a thin top divider.
The layout is static by default; add the
fixedattribute to pin it to the bottom of the screen (position: fixed; bottom: 0— preferably as a direct child ofbodyto avoid being carried away by a scroll container).
Basic usage
Pass a JSON array via items [{ label, value, icon? }]; icon is an icon name from @oas-ui/icons built-in icon set. When value is not set, the first non-disabled item is active by default.
Basic usage
Controlled value
The value attribute is controlled: setting it externally switches the active item, and component interaction also writes the attribute back and fires oas-change.
Controlled switching
Disabled items
disabled items sync aria-disabled, cannot be selected by click, and are skipped by arrow-key navigation.
Disabled items
Fixed to the bottom (fixed)
Add the fixed attribute to pin it to the bottom of the viewport (bottom: 0). This demo page uses a static layout to avoid covering content; use fixed in real mobile scenarios.
fixed demo (kept static here)
Declarative child channel
Besides the items JSON, items can be declared with <oas-bottom-navigation-item> child elements (the items attribute takes priority when explicitly set; otherwise children are parsed and converge to the same rendering path). The default slot text becomes the label; attributes map to the BottomNavItem fields: value / icon / disabled / badge. Child additions/removals, attribute and text changes re-render automatically (MutationObserver).
Declarative children (icon / disabled)
Corner badge (badge)
Add badge (number/text) to an item to render a top-right corner badge overlaid on the icon; unset items render no badge. The badge styling reuses the badge component's tokens (--oas-badge-bg / --oas-badge-on-color, default danger); the items channel and the declarative child channel behave identically.
Badges (items channel)
Badges (child channel)
Safe area (safe-area)
The safe-area boolean attribute (to be combined with fixed): in fixed mode it adds env(safe-area-inset-bottom) bottom padding to avoid the home indicator of notched screens; it has no effect in non-fixed mode. This demo page stays static; use fixed + safe-area on real mobile devices.
safe-area demo (kept static here)
Hide on scroll (hide-on-scroll)
The hide-on-scroll boolean attribute (to be combined with fixed): the bar slides out with translateY(100%) when scrolling down and slides back when scrolling up; the transition only animates transform and never touches layout. A scroll delta >4px decides the direction, avoiding false triggers from tiny jitters.
The hide is purely visual: no
aria-hiddenis added (tab semantics and keyboard focus stay reachable), and it is restored on scroll-back — ideal for giving screen space back while browsing long content, with the navigation still there when you scroll back up.
Hide on scroll (fixed + hide-on-scroll)
Scroll this page: the bottom navigation slides out while scrolling down and slides back while scrolling up. The demo bar is fixed to the viewport bottom (centered, max-width 480px) for demonstration only.
Horizontal layout (layout)
layout="horizontal" puts the icon on the left and the text on the right on the same row (default stacked puts the icon above the text); badges still overlay the icon's top-right corner; keyboard roving and ARIA semantics are unchanged. Invalid values fall back to stacked with a warning.
Horizontal layout (icon left, text right)
Icon-only compact mode (show-label)
show-label="active" shows text only on the selected item; unselected items show just the icon (compact mode). Unselected labels are visually hidden (CSS display: none) while the selected item keeps its text; every tab's accessible name is backed by an auto-written aria-label (the item's label text), so screen readers are unaffected by the CSS hide. Default true shows text on all items; invalid values fall back to true with a warning (deduped).
Icon-only compact mode (text only on the selected item)
Selected-item lift animation (shift)
The shift boolean attribute lifts the selected item: its icon moves up slightly (translateY(-2px)) and scales up (scale 1.08) with the label scaling up too, while unselected items settle back; a pure CSS transition (transform only, no layout impact), disabled under prefers-reduced-motion.
shift selected-item animation
Click to switch the selected item and observe the icon lifting/scaling up and unselected items settling back smoothly.
Pill shape (pill)
pill turns the whole bar into a floating capsule: the tablist is fully rounded, gets a full outline (replacing the top-only divider) and a soft shadow; the host insets itself on both sides and, with fixed, also leaves bottom spacing so it floats on all edges. Insets / shadow are exposed via the --oas-bottom-navigation-pill-inset / --oas-bottom-navigation-pill-shadow variables; dark mode picks up tokens automatically.
Pill shape (static)
Tunable insets / shadow (CSS variables)
On real mobile add
fixed(optionally withsafe-area) for a bottom-floating capsule (all edges inset automatically); this demo stays static so it does not cover the content.
Variable customization
Pure CSS variable openings (no attribute); dark mode picks up tokens automatically:
--oas-bottom-navigation-active-color: active item color, defaults to the primary token--oas-bottom-navigation-height: bar height, defaults to56px(applies in every layout mode, not only fixed)
Variable customization (active color + height)
API
oas-bottom-navigation
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
fixed | Pin to the viewport bottom (position: fixed; bottom: 0) | boolean | — |
hide-on-scroll | Hide on scroll (requires fixed): slides out with translateY(100%) while scrolling down and slides back while scrolling up (a scroll delta >4px decides the direction; the transition animates transform only). A purely visual collapse without aria-hidden (tab semantics and keyboard focus stay reachable), restored on scroll-back; no effect when not fixed | boolean | — |
items | Navigation items JSON | string | [] |
layout | Layout mode: stacked (default, icon above text) / horizontal (icon left, text right, same row); invalid values fall back to stacked with a warning (deduped) | string | stacked |
pill | Pill shape: turns the whole bar into a floating capsule (tablist fully rounded + a full outline replacing the top-only divider + a soft shadow; the host insets itself on both sides and, in fixed mode, leaves bottom spacing to float on all edges). Insets/shadow are exposed via the --oas-bottom-navigation-pill-inset / --oas-bottom-navigation-pill-shadow variables; dark theme follows tokens automatically | — | — |
safe-area | Adds bottom safe-area padding (env(safe-area-inset-bottom)) in fixed mode to avoid the home indicator of notched screens; no effect in non-fixed mode | boolean | — |
shift | Selected-item lift animation: the selected tab's icon lifts slightly (translateY(-2px)) and scales up (scale 1.08) with the label scaling up too, unselected items settle back; pure CSS transition (transform only, no layout impact), disabled under prefers-reduced-motion | boolean | — |
show-label | Label display mode: true (default, all items show text) / active (icon-only compact mode: only the selected item shows text, unselected items show only the icon — each tab's accessible name is backed by an auto-written aria-label from the item label, unaffected by the CSS hide); invalid values fall back to true with a warning (deduped) | string | true |
value | Value of the active item; defaults to the first available item | — | — |
Events
| Event | Description |
|---|---|
oas-change | The active item changed, detail: { value } |
CSS Variables
| CSS Variable | Description | Default |
|---|---|---|
--oas-badge-bg | — | var(--oas-color-danger) |
--oas-badge-on-color | — | var(--oas-color-text-on-danger) |
--oas-bottom-navigation-active-color | — | var(--oas-color-primary) |
--oas-bottom-navigation-height | — | 56px |
--oas-bottom-navigation-pill-inset | — | 12px |
--oas-bottom-navigation-pill-shadow | — | var(--oas-shadow-sm) |
--oas-glass-blur | — | none |
--oas-glass-refraction | Liquid-glass edge refraction filter (active under data-glass, default none); for local opt-out override with an empty value (--oas-glass-refraction: ;)—not none (mixing none with button-state brightness() invalidates the whole filter declaration) | none |
--oas-glass-ring | — | transparent |
oas-bottom-navigation-item
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
badge | Top-right corner badge (number/text overlaid on the icon; not rendered when unset) | — | — |
disabled | Disable this item (not selectable, skipped by keyboard) | — | — |
icon | Leading icon (@oas-ui/icons registry icon name) | — | — |
value | Selection value (data-carrier field of the declarative child channel) | — | — |
Slots
| Name | Description |
|---|---|
| default | Bottom-navigation item label content (default slot text) |
BottomNavItem fields:
| Field | Description | Type |
|---|---|---|
label | Text | string |
value | Value (unique identifier) | string |
icon | Icon name (a key of @oas-ui/icons built-in icon set) | string |
disabled | Disabled (not selectable, skipped by keyboard) | boolean |
badge | Top-right corner badge (number/text overlaid on the icon; not rendered when unset) | string |
Behavior: role="tablist" + role="tab" + synced aria-selected / aria-disabled; roving tabindex keeps only the active item focusable; arrow keys (left/right or up/down) cycle focus among available items (Home/End jump to the ends), Enter/Space selects the focused item; clicking an already-active item does not re-fire; empty items renders an empty tablist without errors. The active item uses the primary color plus an icon (built-in inline SVG following currentColor), with a thin top divider; badge renders a top-right corner badge (overlaid on the icon, badge tokens); fixed + safe-area adds bottom safe-area padding.