Live Preview
Default (Material style)
Liquid Glass (Floating)
--- description: Hướng dẫn Agent tự động tạo UI Component Bottom Navigation — thanh điều hướng cố định ở đáy màn hình mobile. Tích hợp trends iOS 26 Liquid Glass, Material Design 3 Expressive, Samsung OneUI 7.
Bottom Navigation Component Generation Skill
Skill này hướng dẫn bạn (AI Agent) tạo component Bottom Navigation — thanh điều hướng cố định ở đáy màn hình cho mobile apps.
1. Mục tiêu (Objective)
Tạo bottom navigation bar hỗ trợ 3-5 destinations, với active indicator, icon+label, collapsing on scroll, và tương thích với iOS 26 Liquid Glass + Material Design 3 Expressive + Samsung OneUI 7 design patterns.
2. AI Context & Intent (Ngữ cảnh cho AI)
Khi nào dùng Bottom Navigation?
- Mobile primary navigation: 3-5 top-level destinations của app
- Persistent across screens: luôn hiển thị, trừ khi fullscreen content
- Equal importance: mỗi destination ngang hàng, không hierarchy
Phân biệt với component khác
| Tình huống | Component đúng | Lý do |
|---|---|---|
| Mobile top-level navigation (3-5 items) | Bottom Navigation | Persistent, one-thumb reachable |
| Web horizontal navigation | navigation-menu | Desktop-first, wider layout |
| < 3 destinations | Tabs | Quá ít cho bottom nav |
| > 5 destinations | Drawer / Sidebar | Quá nhiều, cần overflow |
| Contextual actions (share, save) | Bottom Bar / Toolbar | Actions ≠ navigation |
| Secondary navigation within a section | Tabs | Scoped, not global |
Decision Tree cho AI
text
Cần navigation cho mobile app?
├─ 3-5 top-level destinations, equal importance → Bottom Navigation
│ ├─ iOS style (liquid glass, floating) → variant="glass"
│ ├─ Material style (chip indicator) → variant="default"
│ └─ OneUI style (bold active, minimal) → variant="minimal"
├─ < 3 destinations → Tabs
├─ > 5 destinations → Drawer + Hamburger
├─ Desktop/web primary nav → navigation-menu
└─ Actions (not navigation) → Bottom Bar (bottom-bar pattern)
3. Platform Trend Research (2025-2026)
3.1. iOS 26 — Liquid Glass Tab Bar
| Feature | Spec |
|---|---|
| Material | Liquid Glass — translucent, refractive |
| Shape | Floating, rounded, inset from edges |
| Height | 49pt content + safe area (34pt home indicator) |
| Active indicator | Glass chip behind active icon |
| Behavior | Collapses on scroll — shows only active tab when scrolling down, expands on scroll up |
| Search tab | Can morph into search field positioned at bottom |
| Tab accessory | "Shelf" above tab bar for mini-players, global status |
| Labels | Always visible (3 tabs) or hidden when inactive (4-5 tabs) |
| Animation | Spring curve on tab switch, glass refraction |
| Backdrop | backdrop-filter: blur(30px) saturate(180%) |
3.2. Material Design 3 Expressive — Navigation Bar
| Feature | Spec |
|---|---|
| Name | "Navigation bar" (renamed from "Bottom navigation") |
| Container height | 80dp (taller than MD2's 56dp) |
| Active indicator | chip shape — contrasting color behind active icon |
| Active icon | Filled variant; inactive = outlined |
| Labels | Always visible (3 items); icon-only when inactive (4-5 items) |
| No shadow | Uses color mapping instead of elevation shadow |
| Expressive update | Shorter, flexible bar; horizontal items for medium windows |
| Re-select | Scrolls back to top of current page |
| Badge | Supports notification badges on icons |
3.3. Samsung OneUI 7 — Bottom Nav
| Feature | Spec |
|---|---|
| Style | "Sophistic Minimalism" — clean, subtle gradients |
| Shape | Full-width, flush to edges (with safe area) |
| Active indicator | Bold icon + label, no chip shape |
| Inactive | Lighter opacity, outlined icons |
| Transparency | Subtle transparent background in dark mode |
| Glass UI (8.5 preview) | Frosted glass panels, transparency effects, coming 2026 |
| Now Bar | Dynamic info strip above nav bar (lock screen only) |
3.4. Cross-platform Convergence Patterns
| Pattern | iOS 26 | MD3 Exp | OneUI 7 |
|---|---|---|---|
| Floating | ✅ | ❌ | ❌ |
| chip indicator | ✅ (glass) | ✅ (color) | ❌ |
| Collapse on scroll | ✅ | ❌ | ❌ |
| Icon filled/outlined | ❌ (same) | ✅ | ✅ |
| Glass/translucent | ✅ | ❌ | ✅ (OneUI 8.5) |
| Always show labels | 3 tabs ✅ | 3 tabs ✅ | ✅ always |
4. Ngữ nghĩa & Phân loại (Semantics)
4.1. Visual Variants
| Variant | Mô tả | Use case | Platform feel |
|---|---|---|---|
default |
Solid background, chip indicator | Standard apps | MD3 Expressive |
glass |
Liquid glass, floating, inset | Premium/luxury | iOS 26 |
minimal |
Flat, bold active, no chip | Clean minimalism | OneUI 7 |
elevated |
Shadow + solid bg | Cards-heavy layouts | Cross-platform |
4.2. Sub-components / Slots
| Part | Mô tả | Tokens chính |
|---|---|---|
| Container | Full-width bar, fixed bottom | --background / --surface, safe area padding |
| Nav Item | Icon + Label, tappable | 44×44 min touch target (a11y), gap: var(--spacing-1) ← S2: 4px grid |
| Active Indicator | Default: chip 64×32 behind icon. Glass: full-item chip wrapping icon+label | Default: --primary-muted. Glass: --input bg + --border-ghost border |
| Icon | 20×20 (all variants) | Default: --primary (active), --muted-foreground (inactive). Glass: --foreground/--muted-foreground |
| Label | Text below icon, Caption tier | --font-size-xs (12px), --font-weight-medium (T20: ≤12px legibility), line-height: 1.5 ← T3 |
| Badge | Notification dot or count | --destructive bg + --destructive-foreground fg (bg/fg pair) |
| Accessory (opt) | Mini-player / status strip above bar | --surface bg |
4.3. Interactive States
| State | Visual | Token |
|---|---|---|
| Idle (inactive) | Outlined icon, muted label | --muted-foreground |
| Active | Filled icon, primary color, chip indicator | --primary, --primary-muted |
| Pressed | Scale down 0.92, haptic | transform: scale(0.92) |
| Hover (web/tablet) | Subtle background | --state-layer-hover |
| Focus (keyboard) | Focus ring | --primary outline |
| Disabled | Reduced opacity | opacity: var(--disabled) |
4.4. Behaviors
| Behavior | Description |
|---|---|
| Persistent | Visible on all top-level screens |
| Hide on sub-screens | Hidden when pushed into detail views |
| Collapse on scroll (glass variant) | Shrinks to show only active tab |
| Re-select = scroll top | Tapping active tab scrolls page to top |
| Badge update | Real-time badge count update |
| Safe area | Respects device safe area (home indicator) |
4.5. Slot Map (Figma ↔ Code)
📎 Source:
slot-manifest.json→navigation-bar· Layer: ground
| Figma Slot | data-slot |
CSS Class | Required | Accepts |
|---|---|---|---|---|
| Root | navigation-bar |
.ground-bottomnav |
✅ | — |
| Item | navigation-bar-item |
.ground-bottomnav__item |
✅ (3-5×) | nav-item (icon + label) |
| Icon | navigation-bar-icon |
.ground-bottomnav__icon |
✅ | icon |
| Label | navigation-bar-label |
.ground-bottomnav__label |
✅ | text |
html
<!-- HTML skeleton with data-slot -->
<nav class="ground-bottomnav" data-slot="navigation-bar" role="navigation" aria-label="Main">
<button class="ground-bottomnav__item ground-bottomnav__item--active" data-slot="navigation-bar-item">
<span class="ground-bottomnav__indicator">
<span class="ground-bottomnav__icon" data-slot="navigation-bar-icon">🏠</span>
</span>
<span class="ground-bottomnav__label" data-slot="navigation-bar-label">Home</span>
</button>
</nav>
5. Token Mapping
📦 Atomic Mapping: See
ATOMIC-MAPPING.mdfor complete token spec.
📦 Atomic Mapping: UI Layer: Ground, Density Tier: — (fixed height)
| Property | Token | Ghi chú |
|---|---|---|
| Container bg | --background |
Solid variant |
| Container bg (glass) | --surface-subtle + backdrop-filter |
Glass variant (Figma: base/surface-subtle) |
| Container fg | --foreground |
|
| Border top | --border |
Separator from content |
| Active icon fg | --primary (default) / --foreground (glass) |
Glass uses foreground, not primary |
| Active label fg | --primary (default) / --foreground (glass) |
|
| Inactive icon fg | --muted-foreground |
|
| Inactive label fg | --muted-foreground |
|
| Indicator bg (default) | --primary-muted |
chip behind active icon |
| Indicator bg (glass) | --input |
Full-item chip (Figma: base/input) |
| Indicator border (glass) | 1px solid var(--border-ghost) |
Figma: base/border-ghost |
| Indicator size (default) | 64×32px | MD3 Expressive spec |
| Indicator radius | --radius-full (default) / --comp-radius-capsule (glass) |
chip shape |
| Badge bg | --destructive |
|
| Badge fg | --destructive-foreground |
bg/fg pair required |
| Icon size | 20px | All variants |
| Unified — same as default | ||
| Label font | Caption tier | --font-size-xs (12px), --font-weight-medium (T20) |
| Label line-height | var(--font-leading-normal, 1.5) |
← T3 rule: caption tier = 1.4-1.5 |
| Item gap (icon→label) | var(--spacing-1) (4px) |
← S2 rule: 4px base unit |
| Item min-width (glass) | 60px | Figma: min-width 60px |
| Touch target | 44×44 min | a11y requirement (R3) |
| Container height | 80px (MD3 Expressive) | Use var(--spacing-20, 80px) |
| Safe area bottom | env(safe-area-inset-bottom) |
iOS/Android |
| Transition | --motion-quick |
Icon/indicator switch |
| Indicator transition | --motion-spring |
Bouncy chip slide |
| z-index | --z-index-fixed |
Above content |
| Elevation (elevated) | --shadow-sm |
Elevated variant only |
Glass variant tokens (Figma-aligned)
| Property | Token | Note |
|---|---|---|
| Container bg | var(--surface-subtle) |
Translucent (Figma: base/surface-subtle) |
| Container border | 1px solid var(--card-border) |
Figma: base/card-border |
| Container radius | var(--comp-radius-capsule) (9999px) |
Full chip capsule |
| Container padding | var(--spacing-2) |
Figma: var(--style-item-padding-small) |
| Container gap | var(--spacing-2) (8px) |
Figma: var(--spacing-2) |
| Backdrop | blur(20px) saturate(180%) |
Hardcode OK (decorative) |
| Active item bg | var(--input) |
Solid chip (Figma: base/input, #FFF light) |
| Active item border | 1px solid var(--border-ghost) |
Figma: base/border-ghost |
| Active item radius | var(--comp-radius-capsule) |
Same as container |
| Active color | var(--foreground) |
Not --primary (Figma: base/foreground) |
| Inactive color | var(--muted-foreground) |
Figma: base/muted-foreground |
| Item min-width | 60px | Figma: min-width 60px |
| Item padding | var(--spacing-2) |
Figma: var(--style-item-padding-xsmall) |
| Icon size | 20px | Figma: sys-component 20×20 |
6. Props & API
typescript
interface BottomNavProps {
/** Visual variant */
variant?: 'default' | 'glass' | 'minimal' | 'elevated';
/** Navigation items (3-5) */
items: BottomNavItem[];
/** Currently active item index */
activeIndex: number;
/** Callback when item tapped */
onSelect: (index: number) => void;
/** Collapse on scroll (glass variant only) */
collapseOnScroll?: boolean;
/** Show labels on inactive items */
showInactiveLabels?: boolean;
}
interface BottomNavItem {
/** Icon (active state — filled) */
icon: ReactNode;
/** Icon for inactive state (outlined) — optional */
iconInactive?: ReactNode;
/** Label text */
label: string;
/** Badge count (0 = dot only, >0 = number) */
badge?: number;
/** Href for navigation */
href?: string;
}
7. Accessibility (a11y)
- Semantic HTML:
<nav role="navigation" aria-label="Main navigation"> - Items:
<a>tags if href,<button>if onClick. Each hasaria-current="page"when active. - Labels: Always in DOM (can be
aria-labelif visually hidden) - Badge:
aria-labelincludes badge count:"Home, 3 notifications" - Focus:
:focus-visiblering on each nav item - Keyboard: Tab to navigate between items, Enter/Space to activate
- Reduced motion:
@media (prefers-reduced-motion: reduce)— disable spring animations - Touch target: Min 44×44px per item (WCAG 2.2 Level AA)
- Contrast: Active/inactive icons must have ≥ 3:1 contrast ratio
8. Best Practices & Rules
- 3-5 items ONLY: < 3 → use tabs; > 5 → use drawer
- Không Hardcode: Mọi spacing, radius, color từ Token — trừ glass backdrop-filter
- Safe area: Always pad bottom by
env(safe-area-inset-bottom)or--safe-area-bottom - No scroll inside nav: Items cố định, không horizontal scroll
- Label length: Max 1 từ (vd: "Home", "Search", "More")
- Glass variant: Floating, inset from edges, rounded — chỉ dùng khi style = liquid/glass
- Active indicator: chip shape cho default/glass, bold text cho minimal
- Re-select: Tapping active item → scroll to top behavior (implement in JS)
- Dark Mode: Tự động qua Mode tokens — glass bg đổi opacity
8.1. Mobile App Rules (B8, B12)
| Rule | ID | Requirement |
|---|---|---|
| No Tooltip | B8 | .tooltip { display: none; }. Mobile không có hover — tooltip bị clip bởi container tạo dark line artifact. |
| Safe Area Token | B12 | padding-bottom PHẢI dùng var(--local-*-safe-area-bottom). KHÔNG hardcode px. Phone frame có rounded corners + home indicator. |
8.2. Indicator chip Positioning
Khi HTML có indicator là sibling (không phải parent) của icon:
html
<button class="ground-bottomnav__item">
<span class="ground-bottomnav__indicator"></span> <!-- sibling -->
<span class="ground-bottomnav__icon">...</span>
<span class="ground-bottomnav__label">Home</span>
</button>
Indicator PHẢI dùng position: absolute để không chiếm flex space, tránh đội cell active cao hơn inactive:
css
.ground-bottomnav__item { position: relative; }
.ground-bottomnav__indicator {
position: absolute; top: 0; left: 50%;
transform: translateX(-50%);
width: 64px; height: 32px;
pointer-events: none;
}
9. Example Usage
jsx
{/* Default (Material style) */}
<BottomNav
variant="default"
items={[
{ icon: <HomeIcon />, label: "Home", badge: 3 },
{ icon: <SearchIcon />, label: "Search" },
{ icon: <WalletIcon />, label: "Wallet" },
{ icon: <ProfileIcon />, label: "Profile" },
]}
activeIndex={0}
onSelect={handleNav}
/>
{/* Glass (iOS 26 Liquid Glass) */}
<BottomNav
variant="glass"
items={items}
activeIndex={activeIndex}
onSelect={handleNav}
collapseOnScroll
/>
{/* Minimal (Samsung OneUI style) */}
<BottomNav
variant="minimal"
showInactiveLabels={false}
items={items}
activeIndex={1}
onSelect={handleNav}
/>
10. CSS Skeleton
css
/* Container — Ground layer */
.ground-bottomnav {
position: fixed;
bottom: 0; left: 0; right: 0;
z-index: var(--z-index-fixed);
background: var(--background);
border-top: 1px solid var(--border);
padding-bottom: env(safe-area-inset-bottom, var(--spacing-8-5));
}
/* Glass variant — floating capsule (Figma-aligned) */
.ground-bottomnav--glass {
position: fixed;
bottom: var(--spacing-2);
left: 50%;
transform: translateX(-50%);
background: var(--surface-subtle);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid var(--card-border);
border-radius: var(--comp-radius-capsule); /* 9999px — full chip */
border-top: none; /* no top separator — floating */
padding: var(--spacing-2);
gap: var(--spacing-2);
}
/* Glass active item — full chip wrapping icon+label */
.ground-bottomnav--glass .ground-bottomnav__item--active {
background: var(--input);
border: 1px solid var(--border-ghost);
border-radius: var(--comp-radius-capsule);
color: var(--foreground);
min-width: 60px;
padding: var(--spacing-2);
}
/* Glass inactive items */
.ground-bottomnav--glass .ground-bottomnav__item {
color: var(--muted-foreground);
min-width: 60px;
padding: var(--spacing-2);
}
/* Glass icon size — 20px per Figma */
.ground-bottomnav--glass .ground-bottomnav__icon {
width: 20px;
height: 20px;
}
/* Glass: no separate indicator chip — active item IS the chip */
.ground-bottomnav--glass .ground-bottomnav__indicator::before {
display: none;
}
/* Nav items container */
.ground-bottomnav__items {
display: flex;
justify-content: space-around;
align-items: center;
height: var(--spacing-20, 80px); /* MD3 Expressive: 80dp */
padding: 0 var(--spacing-2);
}
/* Individual item */
.ground-bottomnav__item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--spacing-1); /* S2: 4px base unit */
min-width: 44px;
min-height: 44px;
padding: 0 var(--spacing-3); /* horizontal only — flex centers vertically */
border: none;
background: none;
color: var(--muted-foreground);
cursor: pointer;
position: relative;
transition: color var(--duration-fast-2) var(--easing-standard);
}
/* Active state */
.ground-bottomnav__item--active {
color: var(--primary);
}
/* Active indicator chip — 64×32 per MD3 Expressive */
.ground-bottomnav__indicator {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 64px;
height: 32px;
}
.ground-bottomnav__item--active .ground-bottomnav__indicator::before {
content: '';
position: absolute;
inset: 0;
background: var(--primary-muted);
color: var(--primary); /* bg/fg pair */
border-radius: var(--radius-full);
transition: all var(--duration-slow-1) var(--easing-spring);
}
/* Icon */
.ground-bottomnav__icon {
position: relative; /* above chip */
width: 20px; /* unified icon size */
height: 20px;
}
/* Label — Caption tier (T3 + §6.1) */
.ground-bottomnav__label {
font-size: var(--font-size-xs); /* 12px — Caption tier (11-12px) */
font-weight: var(--font-weight-medium);
line-height: var(--font-leading-normal, 1.5); /* T3: caption = 1.4-1.5 */
letter-spacing: var(--font-tracking-wider);
}
/* Badge */
.ground-bottomnav__badge {
position: absolute;
top: 0;
right: var(--spacing-1);
min-width: var(--spacing-4);
height: var(--spacing-4);
border-radius: var(--radius-full);
background: var(--destructive);
color: var(--destructive-foreground); /* bg/fg pair */
font-size: var(--font-size-2xs);
font-weight: var(--font-weight-bold);
display: flex;
align-items: center;
justify-content: center;
padding: 0 var(--spacing-1);
}
/* Minimal variant — no chip, bold active */
.ground-bottomnav--minimal .ground-bottomnav__indicator::before {
display: none;
}
.ground-bottomnav--minimal .ground-bottomnav__item--active {
color: var(--foreground);
font-weight: var(--font-weight-bold);
}
/* Reduced motion — M1 rule */
@media (prefers-reduced-motion: reduce) {
.ground-bottomnav__item,
.ground-bottomnav__indicator::before {
transition: none;
}
}