User Journey & Navigation Standards Guide
Status: Complete
Category: Critical UX Standards
Version: 1.0
Based On: V4 Lessons #17, #32-36 - Navigation and Integration Patterns
The Golden Rule
"If it's in the menu, it must exist. If a feature exists, there must be a visible way to reach it."
Navigation failures destroy user trust faster than any bug. This guide ensures users can always find and use your features.
Table of Contents
- Core Navigation Principles
- Navigation Visibility Standards
- Progressive Disclosure Patterns
- Hybrid Integration Strategy
- 404 Prevention Protocol
- User Journey Mapping
- Navigation Testing Standards
- Common Navigation Failures
- Implementation Checklist
Core Navigation Principles
The V4 Navigation Lessons
- Navigation Must Be Immediately Obvious - Users shouldn't hunt for features
- Every Menu Item Must Work - No 404s, no "coming soon"
- Progressive Disclosure for Complexity - Simple tasks simple, advanced tasks accessible
- Hybrid Integration > Full Integration - Wrap complex tools, don't embed them
- Backend Services Don't Need Frontend Embedding - APIs over iframes
Navigation Hierarchy
interface NavigationStructure {
primary: {
visibility: 'always-visible';
position: 'left-sidebar' | 'top-header';
items: MenuItem[];
};
secondary: {
visibility: 'context-dependent';
position: 'sub-menu' | 'dropdown';
items: MenuItem[];
};
actions: {
visibility: 'page-specific';
position: 'floating' | 'inline';
items: ActionItem[];
};
}
interface MenuItem {
label: string;
icon: string;
path: string;
visible: boolean;
enabled: boolean;
badge?: string | number;
subItems?: MenuItem[];
}
Navigation Visibility Standards
Always Visible Navigation
// β WRONG: Hidden by default
const [sidebarOpen, setSidebarOpen] = useState(false);
// β
CORRECT: Visible by default
const [sidebarOpen, setSidebarOpen] = useState(true);
// MANDATORY: Primary navigation always accessible
const PrimaryNavigation = () => {
return (
<nav className="primary-nav" aria-label="Main navigation">
{/* Fixed position, always visible on desktop */}
<div className="fixed left-0 top-0 h-full w-64 bg-white border-r">
<NavigationItems items={primaryMenuItems} />
</div>
{/* Mobile: Hamburger menu but with clear icon */}
<button
className="md:hidden fixed top-4 left-4 z-50"
aria-label="Open navigation menu"
>
<MenuIcon size={24} />
<span className="ml-2">Menu</span> {/* Text label too! */}
</button>
</nav>
);
};
Navigation Item Requirements
// Every navigation item MUST have:
interface NavigationRequirements {
hasIcon: boolean; // Visual recognition
hasLabel: boolean; // Text description
hasPath: boolean; // Working route
hasPage: boolean; // Actual page exists
isAccessible: boolean; // Keyboard navigable
hasFeedback: boolean; // Hover/active states
}
// Validation before adding to menu
function validateMenuItem(item: MenuItem): void {
// 1. Page must exist
if (!pageExists(item.path)) {
throw new Error(`Page ${item.path} does not exist. Create it first!`);
}
// 2. Icon and label required
if (!item.icon || !item.label) {
throw new Error('Menu items must have both icon and label');
}
// 3. Path must be valid
if (!isValidPath(item.path)) {
throw new Error(`Invalid path: ${item.path}`);
}
}
Progressive Disclosure Patterns
The 90/10 Rule
90% of users need simple features. 10% need advanced features. Design for both.
// Progressive disclosure for automation features
interface AutomationInterface {
// Level 1: Simple Toggle (90% of users)
simple: {
component: 'ToggleSwitch';
options: ['On', 'Off'];
complexity: 'none';
};
// Level 2: Basic Configuration (9% of users)
intermediate: {
component: 'FormWithPresets';
options: ['Daily', 'Weekly', 'Custom'];
complexity: 'low';
};
// Level 3: Advanced Builder (1% of users)
advanced: {
component: 'LinkToN8n';
options: 'Full workflow builder';
complexity: 'high';
};
}
Implementation Example
const AutomationSettings = () => {
const [complexityLevel, setComplexityLevel] = useState('simple');
return (
<div className="automation-settings">
{/* Level 1: Always visible simple option */}
<div className="simple-controls">
<h3>Email Automation</h3>
<ToggleSwitch
label="Send welcome emails automatically"
onChange={(enabled) => handleSimpleToggle(enabled)}
/>
</div>
{/* Level 2: Revealed on request */}
{complexityLevel >= 'intermediate' && (
<div className="intermediate-controls">
<h4>Customize Schedule</h4>
<RadioGroup
options={['Immediately', 'After 1 hour', 'After 1 day']}
onChange={(timing) => handleTimingChange(timing)}
/>
</div>
)}
{/* Level 3: Link to advanced tool */}
<div className="advanced-link">
<button
onClick={() => setComplexityLevel('advanced')}
className="text-sm text-blue-600"
>
Need more control?
</button>
{complexityLevel === 'advanced' && (
<div className="advanced-panel">
<p>For complex workflows, use our workflow builder:</p>
<a
href="/workflows/builder"
className="btn btn-primary"
target="_blank"
>
Open Workflow Builder β
</a>
</div>
)}
</div>
</div>
);
};
Hybrid Integration Strategy
Don't Embed Complex Tools - Wrap Them
// β WRONG: Embedding n8n in iframe
const WorkflowBuilder = () => {
return (
<iframe
src="http://localhost:5678"
className="w-full h-screen"
/>
);
// Problems: Auth conflicts, inconsistent UX, mobile issues
};
// β
CORRECT: Native UI calling n8n APIs
const WorkflowBuilder = () => {
return (
<div className="workflow-manager">
{/* Our UI for common tasks */}
<div className="quick-actions">
<h2>Campaign Automations</h2>
<button onClick={() => createWorkflow('welcome-series')}>
Create Welcome Series
</button>
<button onClick={() => createWorkflow('re-engagement')}>
Create Re-engagement Campaign
</button>
</div>
{/* Link to n8n for advanced users */}
<div className="advanced-section">
<h3>Need Custom Workflows?</h3>
<p>Power users can access the full workflow builder</p>
<button
onClick={() => window.open(`${N8N_URL}/workflow/new`, '_blank')}
className="btn-secondary"
>
Open Advanced Builder
</button>
</div>
</div>
);
};
// Backend: n8n as API service
async function createWorkflow(template: string) {
const workflow = await generateWorkflowFromTemplate(template);
const response = await fetch(`${N8N_URL}/api/v1/workflows`, {
method: 'POST',
headers: {
'X-N8N-API-KEY': process.env.N8N_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(workflow)
});
return response.json();
}
Integration Decision Matrix
interface IntegrationDecision {
tool: string;
complexity: 'low' | 'medium' | 'high';
userFrequency: 'rare' | 'occasional' | 'frequent';
recommendation: 'embed' | 'wrap' | 'link';
}
const integrationStrategy: IntegrationDecision[] = [
{
tool: 'Simple form',
complexity: 'low',
userFrequency: 'frequent',
recommendation: 'embed' // Build natively
},
{
tool: 'Email composer',
complexity: 'medium',
userFrequency: 'frequent',
recommendation: 'wrap' // Our UI, external processing
},
{
tool: 'Workflow builder',
complexity: 'high',
userFrequency: 'rare',
recommendation: 'link' // Link to specialized tool
}
];
404 Prevention Protocol
Never Create Broken Links
// Page creation validation
class NavigationManager {
private routes: Map<string, boolean> = new Map();
// Check page exists BEFORE adding to navigation
async addNavigationItem(item: MenuItem): Promise<void> {
// 1. Verify page exists
const pageExists = await this.checkPageExists(item.path);
if (!pageExists) {
// 2. Create minimal page if missing
await this.createMinimalPage(item.path);
}
// 3. Only then add to navigation
this.navigationItems.push(item);
this.routes.set(item.path, true);
}
async createMinimalPage(path: string): Promise<void> {
const pageName = path.split('/').pop();
// Create actual page file
const pageContent = `
export default function ${pageName}Page() {
return (
<div className="container mx-auto p-6">
<h1 className="text-2xl font-bold mb-4">${pageName}</h1>
<p className="text-gray-600">This feature is being developed.</p>
<div className="mt-8 p-4 bg-blue-50 rounded">
<p className="text-sm">Expected completion: Coming soon</p>
</div>
</div>
);
}`;
await createPageFile(path, pageContent);
}
}
404 Testing Automation
// Automated navigation testing
async function test404Prevention(): Promise<TestResult> {
const results: TestResult[] = [];
// Get all navigation items
const menuItems = getAllNavigationItems();
for (const item of menuItems) {
try {
// Test each link
const response = await fetch(item.path);
results.push({
path: item.path,
status: response.status,
passed: response.status !== 404,
message: response.status === 404 ?
`CRITICAL: ${item.path} returns 404!` :
`β ${item.path} works`
});
} catch (error) {
results.push({
path: item.path,
status: 'error',
passed: false,
message: `Failed to test ${item.path}: ${error.message}`
});
}
}
const failures = results.filter(r => !r.passed);
if (failures.length > 0) {
throw new Error(`Navigation test failed: ${failures.length} broken links found`);
}
return { passed: true, results };
}
User Journey Mapping
Critical User Journeys
interface UserJourney {
name: string;
steps: JourneyStep[];
successCriteria: string[];
maxClicks: number;
}
interface JourneyStep {
action: string;
location: string;
expectedResult: string;
navigationPath: string[];
}
// Example: New user creating first campaign
const newUserJourney: UserJourney = {
name: 'First Campaign Creation',
maxClicks: 3, // Should take no more than 3 clicks
steps: [
{
action: 'Land on dashboard',
location: '/dashboard',
expectedResult: 'See welcome message and clear CTA',
navigationPath: ['/']
},
{
action: 'Click "Create Campaign"',
location: '/dashboard',
expectedResult: 'Navigate to campaign creation',
navigationPath: ['/', '/campaigns/new']
},
{
action: 'Enter campaign details',
location: '/campaigns/new',
expectedResult: 'Campaign created with real ID',
navigationPath: ['/', '/campaigns/new', '/campaigns/:id']
}
],
successCriteria: [
'User reaches goal in β€3 clicks',
'No confusion about next step',
'Real campaign created in database',
'Success message displayed'
]
};
Journey Validation
async function validateUserJourney(journey: UserJourney): Promise<boolean> {
const session = await createTestSession();
let currentPath = '/';
let clickCount = 0;
for (const step of journey.steps) {
// Navigate to step location
await session.navigateTo(step.location);
// Verify expected elements exist
const element = await session.findElement(step.action);
if (!element) {
throw new Error(`Step failed: Cannot find "${step.action}" at ${step.location}`);
}
// Perform action
await element.click();
clickCount++;
// Verify result
const result = await session.verifyResult(step.expectedResult);
if (!result.success) {
throw new Error(`Step failed: ${result.message}`);
}
}
// Validate success criteria
if (clickCount > journey.maxClicks) {
throw new Error(`Journey too complex: ${clickCount} clicks (max: ${journey.maxClicks})`);
}
return true;
}
Navigation Testing Standards
Manual Testing Checklist
## Navigation Testing Checklist
### Primary Navigation
- [ ] All menu items visible on load
- [ ] Icons and labels present for all items
- [ ] Hover states work on all items
- [ ] Active state shows current page
- [ ] Keyboard navigation works (Tab, Enter)
- [ ] Mobile menu accessible and functional
### Link Testing
- [ ] Every menu item leads somewhere (no 404s)
- [ ] Back button works from all pages
- [ ] Breadcrumbs accurate (if present)
- [ ] Deep links work when shared
### User Journey Testing
- [ ] New user can create first item in <3 clicks
- [ ] Common tasks accessible from dashboard
- [ ] Search/filter helps find features
- [ ] Help is accessible from every page
Automated Testing
// Playwright/Cypress test example
describe('Navigation Standards', () => {
it('should have no broken links', async () => {
await page.goto('/');
// Get all navigation links
const links = await page.$eval('nav a', elements =>
elements.map(el => el.href)
);
// Test each link
for (const link of links) {
const response = await page.goto(link);
expect(response.status()).not.toBe(404);
}
});
it('should complete user journey in max clicks', async () => {
await page.goto('/dashboard');
// Test campaign creation journey
await page.click('text=Create Campaign');
expect(page.url()).toContain('/campaigns/new');
await page.fill('[name=campaignName]', 'Test Campaign');
await page.click('text=Create');
// Should have real campaign ID in URL
expect(page.url()).toMatch(/\/campaigns\/[a-f0-9-]{36}/);
});
});
Common Navigation Failures
Failure 1: Hidden Primary Navigation
Problem: Sidebar hidden by default
Impact: Users can't find features
Solution: Default to visible, allow hiding
Failure 2: Menu Items Without Pages
Problem: Clicking menu item β 404
Impact: Immediate trust loss
Solution: Create page before adding to menu
Failure 3: Complex Tools Embedded
Problem: iframe of n8n/other tools
Impact: Auth conflicts, poor UX
Solution: Native UI with API integration
Failure 4: No Progressive Disclosure
Problem: All complexity shown at once
Impact: Overwhelming for new users
Solution: Simple defaults, advanced on request
Failure 5: Mobile Navigation Broken
Problem: Desktop-only navigation
Impact: Mobile users can't access features
Solution: Responsive navigation patterns
Implementation Checklist
Pre-Development
- Map all user journeys
- Define navigation hierarchy
- Create page stubs for all routes
- Design progressive disclosure strategy
- Plan integration approach (embed/wrap/link)
During Development
- Navigation visible by default
- Every menu item has working page
- Progressive disclosure implemented
- Hybrid integration for complex tools
- Mobile navigation tested
Pre-Launch Testing
- No 404s in navigation
- All journeys β€3 clicks
- Keyboard navigation works
- Mobile experience validated
- User testing confirms findability
Post-Launch Monitoring
- 404 error tracking
- User journey analytics
- Navigation click heatmaps
- User feedback on findability
- Time to complete tasks
Success Metrics
Quantitative Metrics
- 404 Rate: 0% from navigation
- Journey Completion: β€3 clicks
- Feature Discovery: >90% find primary features
- Mobile Usage: Navigation works for 100%
- Task Success Rate: >95%
Qualitative Metrics
- "I found it immediately"
- "The interface is intuitive"
- "I didn't need instructions"
- "It works like I expected"
- "Mobile experience is great"
The Three Laws of Navigation
- If it's in the menu, it must work - No broken promises
- If it exists, it must be findable - No hidden features
- Simple tasks must be simple - Progressive complexity
Follow these laws and users will love your product.
Based on hard-learned lessons from V4 where missing pages and hidden features frustrated users despite working backend functionality.