Skip to content

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 fixed attribute to pin it to the bottom of the screen (position: fixed; bottom: 0 — preferably as a direct child of body to 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

HomeFavoritesMine

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)

HomeDiscoverMine

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)

HomeMessagesMine

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-hidden is 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.

Long-page scroll area (try scrolling the page)

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 with safe-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 to 56px (applies in every layout mode, not only fixed)

Variable customization (active color + height)

API ​

oas-bottom-navigation ​

Attributes ​

AttributeDescriptionTypeDefault
fixedPin to the viewport bottom (position: fixed; bottom: 0)boolean—
hide-on-scrollHide 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 fixedboolean—
itemsNavigation items JSONstring[]
layoutLayout mode: stacked (default, icon above text) / horizontal (icon left, text right, same row); invalid values fall back to stacked with a warning (deduped)stringstacked
pillPill 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-areaAdds 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 modeboolean—
shiftSelected-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-motionboolean—
show-labelLabel 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)stringtrue
valueValue of the active item; defaults to the first available item——

Events ​

EventDescription
oas-changeThe active item changed, detail: { value }

CSS Variables ​

CSS VariableDescriptionDefault
--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-refractionLiquid-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 ​

AttributeDescriptionTypeDefault
badgeTop-right corner badge (number/text overlaid on the icon; not rendered when unset)——
disabledDisable this item (not selectable, skipped by keyboard)——
iconLeading icon (@oas-ui/icons registry icon name)——
valueSelection value (data-carrier field of the declarative child channel)——

Slots ​

NameDescription
defaultBottom-navigation item label content (default slot text)

BottomNavItem fields:

FieldDescriptionType
labelTextstring
valueValue (unique identifier)string
iconIcon name (a key of @oas-ui/icons built-in icon set)string
disabledDisabled (not selectable, skipped by keyboard)boolean
badgeTop-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.

Released under the MIT OR Apache-2.0 License.