Developer Docs
Quick reference guides with props tables, usage examples, and best practices for rapid development
SecondaryNav
Tab navigation component with 4 visual variants, responsive design, and icon support
SecondaryNav
Standardized secondary navigation component for tab-based interfaces. Features 4 visual variants, 3 sizes, responsive behavior, and HMR state persistence.
Quick Example
🔧 Props Reference
All available props for the SecondaryNav component:
| Prop | Type | Default | Description |
|---|---|---|---|
| tabs | Array | [] | Array of tab objects (see Tab Object below) |
| activeTab | van.State | van.state(tabs[0]?.id) | Currently active tab ID state |
| defaultTab | string | tabs[0]?.id | Initial active tab ID |
| variant | string | 'default' | Style variant: 'default', 'pills', 'underline', 'cards' |
| size | string | 'md' | Size variant: 'sm', 'md', 'lg' |
| responsive | boolean | true | Enable responsive mobile abbreviations |
| showDescription | boolean | false | Show tab descriptions (cards variant) |
| onTabChange | Function | null | Callback when tab changes: (tabId) => {} |
| class | string | '' | Additional CSS classes |
Tab Object Structure
| Prop | Type | Default | Description |
|---|---|---|---|
| id | string | required | Unique tab identifier |
| label | string | required | Display text for the tab |
| icon | string | undefined | Oracle JET icon class (e.g., "oj-ux-ico-home") |
| description | string | undefined | Optional description (requires showDescription=true) |
💻 Basic Usage
Simple example showing basic SecondaryNav usage with tabs:
const activeTab = van.state('overview');
const tabs = [
{ id: 'overview', label: 'Overview', icon: 'oj-ux-ico-dashboard' },
{ id: 'analytics', label: 'Analytics', icon: 'oj-ux-ico-bar-chart' },
{ id: 'reports', label: 'Reports', icon: 'oj-ux-ico-document' }
];
SecondaryNav({
tabs,
activeTab
})🎨 Variants
SecondaryNav supports 4 visual variants for different use cases:
Default
Sky background for active tabs, white for inactive
SecondaryNav({
tabs,
variant: 'default'
})Pills
White pills in cloud container, shadow on active
SecondaryNav({
tabs,
variant: 'pills'
})Underline
Sunrise bottom border on active tab
SecondaryNav({
tabs,
variant: 'underline'
})Cards
Gradient cards in grid layout with icons and descriptions
SecondaryNav({
tabs: [
{
id: 'feed',
label: 'Home Feed',
icon: 'oj-ux-ico-home',
description: "See what's happening"
}
],
variant: 'cards',
showDescription: true
})📏 Size Variants
SecondaryNav supports three size options to match your design needs:
Small
SecondaryNav({ tabs, size: 'sm' })Medium (Default)
SecondaryNav({ tabs, size: 'md' })Large
SecondaryNav({ tabs, size: 'lg' })🔄 Tab Change Handler
Use the onTabChange callback to respond to tab changes and update your application state:
const activeTab = van.state('all');
const items = van.state([]);
SecondaryNav({
tabs: [
{ id: 'all', label: 'All' },
{ id: 'active', label: 'Active' },
{ id: 'archived', label: 'Archived' }
],
activeTab,
onTabChange: (tabId) => {
console.log('Tab changed to:', tabId);
// Filter items based on tab
switch (tabId) {
case 'all':
items.val = allItems;
break;
case 'active':
items.val = allItems.filter(item => item.active);
break;
case 'archived':
items.val = allItems.filter(item => item.archived);
break;
}
}
})📱 Responsive Behavior
SecondaryNav adapts to mobile screens by default (responsive=true):
Mobile Abbreviations
✓Long labels show first word only on mobile
✓Full labels visible on desktop (sm:inline)
✓Descriptions hidden on mobile (hidden md:inline)
✓Horizontal scrolling enabled if needed
// Example: "Service Dashboard" → "Service" on mobile
{ id: 'dashboard', label: 'Service Dashboard' }
// Disable responsive behavior:
SecondaryNav({ tabs, responsive: false })🔥 HMR State Persistence
SecondaryNav preserves active tab across hot module reloads:
// Internal state stored globally
window.__secondaryNavState = {
[navId]: activeTab
};
// Benefits:
// - Save code → tab stays active
// - Fast iteration without losing context
// - Automatic across all SecondaryNav instances⚡️ Best Practices
✓ Do This
✓Use unique tab IDs
✓Keep labels short (1-2 words)
✓Use icons for visual scanning
✓Match variant to context (pills for filters)
✓Provide onTabChange handler
✓Test responsive behavior
✓Use descriptions for cards variant
✗ Avoid This
✗Use duplicate tab IDs
✗Have too many tabs (>6)
✗Use long labels (causes mobile wrapping)
✗Forget onTabChange callback
✗Mix SecondaryNav with NavigationList
✗Forget defaultTab prop
✗Use descriptions without showDescription=true
✨ Key Features
4 Visual Variants
Default, pills, underline, and rich card styles
3 Size Options
Small, medium, and large to match your design
Responsive Mobile
Auto-abbreviates labels, horizontal scroll
HMR Persistence
Tab state survives hot reloads
Accessible
ARIA roles, tab semantics, keyboard nav
Icon Support
Oracle JET icons for better visual hierarchy
💡 Common Use Cases
Common patterns for implementing SecondaryNav in your applications:
1. Filter Tabs (Pills Variant)
const activeFilter = van.state('all');
const items = van.state([]);
div(
// Filter tabs
SecondaryNav({
tabs: [
{ id: 'all', label: 'All' },
{ id: 'active', label: 'Active' },
{ id: 'paused', label: 'Paused' }
],
activeTab: activeFilter,
variant: 'pills',
onTabChange: (status) => {
items.val = allItems.filter(item => item.status === status);
}
}),
// Filtered content
() => items.val.map(item => ItemCard(item))
)2. Dashboard Sections (Default Variant)
const activeSection = van.state('overview');
div(
SecondaryNav({
tabs: [
{ id: 'overview', label: 'Overview', icon: 'oj-ux-ico-dashboard' },
{ id: 'analytics', label: 'Analytics', icon: 'oj-ux-ico-bar-chart' },
{ id: 'reports', label: 'Reports', icon: 'oj-ux-ico-document' }
],
activeTab: activeSection
}),
// Section content
() => {
switch (activeSection.val) {
case 'overview': return OverviewSection();
case 'analytics': return AnalyticsSection();
case 'reports': return ReportsSection();
}
}
)3. Settings Categories (Underline Variant)
const activeSetting = van.state('profile');
div(
SecondaryNav({
tabs: [
{ id: 'profile', label: 'Profile', icon: 'oj-ux-ico-user' },
{ id: 'security', label: 'Security', icon: 'oj-ux-ico-lock' },
{ id: 'notifications', label: 'Notifications', icon: 'oj-ux-ico-bell' },
{ id: 'billing', label: 'Billing', icon: 'oj-ux-ico-credit-card' }
],
activeTab: activeSetting,
variant: 'underline'
}),
// Settings content
() => {
switch (activeSetting.val) {
case 'profile': return ProfileSettings();
case 'security': return SecuritySettings();
case 'notifications': return NotificationSettings();
case 'billing': return BillingSettings();
}
}
)4. Dashboard Cards (Cards Variant)
const activeView = van.state('feed');
SecondaryNav({
tabs: [
{
id: 'feed',
label: 'Home Feed',
icon: 'oj-ux-ico-home',
description: "See what's happening in your neighborhood"
},
{
id: 'services',
label: 'Services',
icon: 'oj-ux-ico-grid',
description: 'Browse available services and offers'
},
{
id: 'neighbors',
label: 'Neighbors',
icon: 'oj-ux-ico-people',
description: 'Connect with people nearby'
},
{
id: 'events',
label: 'Events',
icon: 'oj-ux-ico-calendar',
description: 'Find local events and gatherings'
}
],
activeTab: activeView,
variant: 'cards',
showDescription: true
})📚 Complete Example
Full working example showing SecondaryNav with data fetching and state management:
import van from 'vanjs-core';
import { SecondaryNav } from 'shared-components/layout/SecondaryNav.js';
const { div, h1 } = van.tags;
export const ServicesPage = () => {
// State
const activeTab = van.state('all');
const services = van.state([]);
// Tabs
const tabs = [
{ id: 'all', label: 'All Services' },
{ id: 'mine', label: 'My Services' },
{ id: 'favorites', label: 'Favorites' }
];
// Load data
const loadServices = async (filter) => {
const data = await fetch(`/api/services?filter=${filter}`);
services.val = await data.json();
};
return div(
{ class: 'container mx-auto p-4' },
h1({ class: 'text-3xl font-bold mb-6' }, 'Services'),
// Navigation tabs
SecondaryNav({
tabs,
activeTab,
variant: 'pills',
onTabChange: (tabId) => {
loadServices(tabId);
}
}),
// Content
div(
{ class: 'mt-6 grid md:grid-cols-3 gap-4' },
() => services.val.map(service => ServiceCard(service))
)
);
};Explore More Components
Browse other component categories