UIPackage
Menu

Bottom Navigation

bottom-navigation ui
Edit on GitHub

Mobile bottom tab bar with icon + label items. Supports v-model for the active item, an active color, fixed positioning at the viewport bottom, badges on items, and a `to` prop for vue-router integration.

Also available for React ->

Installation

$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/bottom-navigation.json
Named registry: npx shadcn-vue@latest add @uipkge/bottom-navigation Installs to: app/components/ui/bottom-navigation/

Examples

Props

Name Type / Values Default Required
items

Tab items.

BottomNavItem[] required
modelValue

Active item value (v-model).

string '' optional
activeColor

Active item color — a Tailwind text color class. Default 'text-primary'.

string 'text-primary' optional
fixed

Fixed positioning at the viewport bottom. Default true.

boolean true optional
showIndicator

Show an active indicator pill behind the icon. Default true.

boolean true optional
safeArea

Safe-area padding for notched devices (iOS). Default true.

boolean true optional
class HTMLAttributes['class'] optional

Schema

Type aliases from this item's source — use them to shape the data you pass in.

BottomNavItem
interface BottomNavItem {
  /** Unique value identifying this tab. Used with v-model. */
  value: string
  /** Label shown under the icon. */
  label: string
  /** Lucide icon component. */
  icon: Component
  /** Optional badge count or text shown on the icon. */
  badge?: string | number
  /** Router link destination (use with vue-router). */
  to?: string
}

npm dependencies

Files installed (2)

  • app/components/ui/bottom-navigation/BottomNavigation.vue 3.5 kB
    <script setup lang="ts">
    import { computed, type Component, type HTMLAttributes } from 'vue'
    import { cn } from '@/lib/utils'
    
    export interface BottomNavItem {
      /** Unique value identifying this tab. Used with v-model. */
      value: string
      /** Label shown under the icon. */
      label: string
      /** Lucide icon component. */
      icon: Component
      /** Optional badge count or text shown on the icon. */
      badge?: string | number
      /** Router link destination (use with vue-router). */
      to?: string
    }
    
    interface Props {
      /** Tab items. */
      items: BottomNavItem[]
      /** Active item value (v-model). */
      modelValue?: string
      /** Active item color — a Tailwind text color class. Default 'text-primary'. */
      activeColor?: string
      /** Fixed positioning at the viewport bottom. Default true. */
      fixed?: boolean
      /** Show an active indicator pill behind the icon. Default true. */
      showIndicator?: boolean
      /** Safe-area padding for notched devices (iOS). Default true. */
      safeArea?: boolean
      class?: HTMLAttributes['class']
    }
    
    const props = withDefaults(defineProps<Props>(), {
      modelValue: '',
      activeColor: 'text-primary',
      fixed: true,
      showIndicator: true,
      safeArea: true,
    })
    
    const emit = defineEmits<{
      'update:modelValue': [value: string]
      select: [item: BottomNavItem]
    }>()
    
    const active = computed(() => props.modelValue)
    
    function onSelect(item: BottomNavItem) {
      if (item.to) {
        // Router integration — navigate if vue-router is available
        const router = (window as any).__vueRouter
        if (router) router.push(item.to)
      }
      emit('update:modelValue', item.value)
      emit('select', item)
    }
    </script>
    
    <template>
      <nav
        data-uipkge
        data-slot="bottom-navigation"
        :data-fixed="fixed ? '' : undefined"
        :class="
          cn(
            'border-border bg-background/95 z-50 flex items-stretch justify-around border-t backdrop-blur-sm',
            fixed ? 'fixed inset-x-0 bottom-0' : 'relative',
            safeArea && 'pb-[env(safe-area-inset-bottom)]',
            props.class,
          )
        "
      >
        <button
          v-for="item in items"
          :key="item.value"
          data-slot="bottom-navigation-item"
          :data-active="active === item.value ? '' : undefined"
          :aria-current="active === item.value ? 'page' : undefined"
          type="button"
          class="focus-visible:ring-ring/50 relative flex flex-1 flex-col items-center justify-center gap-0.5 pt-2 pb-1.5 text-xs transition-colors duration-200 outline-none focus-visible:ring-[3px]"
          :class="active === item.value ? activeColor : 'text-muted-foreground hover:text-foreground'"
          @click="onSelect(item)"
        >
          <!-- Active indicator pill -->
          <span
            v-if="showIndicator && active === item.value"
            class="bg-primary/10 absolute top-1 h-8 w-14 rounded-full transition-opacity duration-200"
            aria-hidden="true"
          />
          <span class="relative flex items-center justify-center">
            <component
              :is="item.icon"
              class="size-5 transition-transform duration-200"
              :class="active === item.value ? 'scale-110' : ''"
            />
            <span
              v-if="item.badge !== undefined && item.badge !== ''"
              class="bg-destructive text-destructive-foreground absolute -top-1.5 -right-2 flex min-w-4 items-center justify-center rounded-full px-1 text-[10px] leading-4 font-medium"
            >
              {{ item.badge }}
            </span>
          </span>
          <span
            class="max-w-full truncate px-1 transition-opacity duration-200"
            :class="active === item.value ? 'font-medium' : ''"
          >
            {{ item.label }}
          </span>
        </button>
      </nav>
    </template>
  • app/components/ui/bottom-navigation/index.ts 0.1 kB
    export { default as BottomNavigation } from './BottomNavigation.vue'
    export type { BottomNavItem } from './BottomNavigation.vue'

Raw manifest: https://uipkge.dev/r/vue/bottom-navigation.json