Bottom Navigation
bottom-navigation ui 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
$ pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/bottom-navigation.json $ npx shadcn-vue@latest add https://uipkge.dev/r/vue/bottom-navigation.json $ yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/bottom-navigation.json $ bunx 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