Production Patterns
Production-ready patterns with live, editable code examples
Form with Validation
FormsReal-time validation with error states and success feedback
// Simple Form Pattern
const { div, h3, p, form } = van.tags;
// Form state
const email = van.state('');
const password = van.state('');
const emailError = van.state('');
const passwordError = van.state('');
const submitStatus = van.state('');
// Validation function
const validateForm = () => {
let isValid = true;
if (!email.val) {
emailError.val = 'Email is required';
isValid = false;
} else if (!email.val.includes('@')) {
emailError.val = 'Please enter a valid email';
isValid = false;
} else {
emailError.val = '';
}
if (!password.val) {
passwordError.val = 'Password is required';
isValid = false;
} else if (password.val.length < 8) {
passwordError.val = 'Password must be at least 8 characters';
isValid = false;
} else {
passwordError.val = '';
}
return isValid;
};
// Form submission
const handleSubmit = (e) => {
e.preventDefault();
if (validateForm()) {
submitStatus.val = 'Form submitted successfully!';
// Reset form
setTimeout(() => {
email.val = '';
password.val = '';
submitStatus.val = '';
}, 2000);
}
};
return div(
{ class: 'space-y-4' },
h3({ class: 'text-lg font-semibold mb-2' }, 'Simple Form Pattern'),
form(
{
class: 'space-y-4',
onsubmit: handleSubmit
},
Input({
id: 'simple-form-email',
label: 'Email',
type: 'email',
value: email,
message: () => emailError.val ? { code: 'error', detail: emailError.val } : null,
placeholder: 'you@example.com',
required: true,
oninput: e => {
email.val = e.target.value;
emailError.val = '';
}
}),
Input({
id: 'simple-form-password',
label: 'Password',
type: 'password',
value: password,
message: () => passwordError.val ? { code: 'error', detail: passwordError.val } : null,
placeholder: 'Enter password',
required: true,
oninput: e => {
password.val = e.target.value;
passwordError.val = '';
}
}),
Button({
type: 'submit',
class: 'vwfcolor-sky solid vwfsize-md w-full'
}, 'Submit'),
() => submitStatus.val ? p({ class: 'text-success text-sm' }, submitStatus.val) : ''
)
);▶️ Live Preview
Simple Form Pattern
💡 Click "Edit Code" to modify the example
🔄 Click "Apply Changes" to see your modifications
↩️ Click "Reset" to restore original code
Dynamic List Management
StateAdd, remove, and update items in a reactive list
// Working with arrays and objects in state
const items = van.state(['Apple', 'Banana', 'Orange']);
const newItem = van.state('');
const filter = van.state('');
const addItem = () => {
if (newItem.val.trim()) {
// Create new array to trigger reactivity
items.val = [...items.val, newItem.val.trim()];
newItem.val = '';
}
};
const removeItem = (index) => {
// Filter creates new array, triggering update
items.val = items.val.filter((_, i) => i !== index);
};
const clearAll = () => {
items.val = [];
};
// Derived filtered items
const filteredItems = () => {
const searchTerm = filter.val.toLowerCase();
return searchTerm ?
items.val.filter(item => item.toLowerCase().includes(searchTerm)) :
items.val;
};
return div(
{ class: 'space-y-4 p-4' },
h3({ class: 'text-lg font-semibold text-midnight' }, 'Arrays in State'),
// Add item form
div({ class: 'flex gap-2' },
input({
type: 'text',
placeholder: 'Add new item...',
class: 'flex-1 px-3 py-2 border border-mist rounded-lg',
value: newItem,
oninput: (e) => newItem.val = e.target.value,
onkeypress: (e) => e.key === 'Enter' && addItem()
}),
Button({
class: 'vwfcolor-sky solid vwfsize-md',
onclick: addItem
}, 'Add Item'),
Button({
class: 'vwfcolor-sunset outerline vwfsize-md',
onclick: clearAll,
disabled: () => items.val.length === 0
}, 'Clear All')
),
// Filter input
input({
type: 'text',
placeholder: 'Filter items...',
class: 'w-full px-3 py-2 border border-mist rounded-lg',
value: filter,
oninput: (e) => filter.val = e.target.value
}),
// Display stats
div({ class: 'flex gap-4 text-sm text-smoke' },
span('Total items: ', () => items.val.length),
span('Filtered: ', () => filteredItems().length)
),
// Reactive list rendering with filtered items
div({ class: 'space-y-2' },
() => filteredItems().length > 0 ?
div({ class: 'space-y-2' },
...filteredItems().map((item, index) =>
div({ class: 'flex items-center justify-between p-3 bg-white rounded-lg shadow-sm border border-mist' },
span({ class: 'font-medium' }, item),
Button({
class: 'vwfcolor-sunset ghost vwfsize-sm',
onclick: () => removeItem(items.val.indexOf(item))
}, '✕')
)
)
) :
div({ class: 'text-center text-smoke py-8' },
filter.val ? 'No items match your filter' : 'No items yet. Add one above!'
)
)
);▶️ Live Preview
Arrays in State
💡 Click "Edit Code" to modify the example
🔄 Click "Apply Changes" to see your modifications
↩️ Click "Reset" to restore original code
Polling & Auto-Refresh
PerformanceAutomatically update data at regular intervals
// Polling Pattern
const { div, h3, p } = van.tags;
// State
const status = van.state({
users: Math.floor(Math.random() * 100) + 50,
active: Math.floor(Math.random() * 30) + 10,
lastUpdate: new Date().toLocaleTimeString()
});
const isPolling = van.state(true);
// Poll for updates
let pollInterval = setInterval(() => {
if (isPolling.val) {
status.val = {
users: Math.floor(Math.random() * 100) + 50,
active: Math.floor(Math.random() * 30) + 10,
lastUpdate: new Date().toLocaleTimeString()
};
}
}, 3000); // Update every 3 seconds
// Toggle polling
const togglePolling = () => {
isPolling.val = !isPolling.val;
};
return div(
{ class: 'space-y-4' },
h3({ class: 'text-lg font-semibold mb-2' }, 'Polling Pattern'),
div({ class: 'flex gap-2 items-center mb-4' },
Button({
class: () => isPolling.val ? 'subtle vwfcolor-error vwfsize-sm' : 'subtle vwfcolor-success vwfsize-sm',
onclick: togglePolling
}, () => isPolling.val ? 'Stop Polling' : 'Start Polling'),
() => isPolling.val ? Badge({ class: 'subtle vwfcolor-success vwfsize-xs' }, '● Live') : ''
),
Card({ class: 'p-4 bg-white space-y-3' },
div({ class: 'flex justify-between items-center' },
div(
p({ class: 'text-sm text-secondary' }, 'Total Users'),
p({ class: 'text-2xl font-bold' }, () => status.val.users)
),
div(
p({ class: 'text-sm text-secondary' }, 'Active Now'),
p({ class: 'text-2xl font-bold text-success' }, () => status.val.active)
)
),
p({ class: 'text-xs text-secondary pt-3 border-t border-mist' },
'Last updated: ', () => status.val.lastUpdate
)
),
p({ class: 'text-xs text-secondary' },
'Updates every 3 seconds when polling is enabled'
)
);▶️ Live Preview
Polling Pattern
Total Users
140
Active Now
38
Last updated: 3:59:07 PM
Updates every 3 seconds when polling is enabled
💡 Click "Edit Code" to modify the example
🔄 Click "Apply Changes" to see your modifications
↩️ Click "Reset" to restore original code
Modal Dialog
ComponentsReusable modal with backdrop and animations
// Basic Modal Pattern
const { div, h3, p } = van.tags;
// Modal state
const isOpen = van.state(false);
// Open/close handlers
const openModal = () => isOpen.val = true;
const closeModal = () => isOpen.val = false;
return div(
{ class: 'space-y-4' },
h3({ class: 'text-lg font-semibold mb-2' }, 'Basic Modal'),
Button({
class: 'solid vwfcolor-sky vwfsize-md',
onclick: openModal
}, 'Open Modal'),
// Modal
() => isOpen.val ? div(
{ class: 'fixed inset-0 bg-midnight/50 flex items-center justify-center z-50' },
Card({ class: 'bg-white p-6 rounded-2xl shadow-xl max-w-md mx-4' },
h3({ class: 'text-xl font-semibold mb-4' }, 'Modal Title'),
p({ class: 'text-secondary mb-6' },
'This is a basic modal dialog. It overlays the main content and requires user interaction to close.'
),
div({ class: 'flex gap-2 justify-end' },
Button({
class: 'subtle vwfcolor-fog vwfsize-sm',
onclick: closeModal
}, 'Cancel'),
Button({
class: 'solid vwfcolor-sky vwfsize-sm',
onclick: () => {
alert('Action confirmed!');
closeModal();
}
}, 'Confirm')
)
)
) : ''
);▶️ Live Preview
Basic Modal
💡 Click "Edit Code" to modify the example
🔄 Click "Apply Changes" to see your modifications
↩️ Click "Reset" to restore original code
Tabs Component
NavigationTab navigation with content panels
// Tabs Pattern
const { div, h3, button, p, span } = van.tags;
// Active tab
const activeTab = van.state('overview');
// Tab content
const tabs = {
overview: {
label: 'Overview',
icon: '📊',
content: 'This is the overview tab with general information and statistics.'
},
activity: {
label: 'Activity',
icon: '📈',
count: 12,
content: 'Recent activity feed showing latest updates and interactions.'
},
settings: {
label: 'Settings',
icon: '⚙️',
content: 'Configuration and preferences for your account.'
}
};
return div(
{ class: 'space-y-4' },
h3({ class: 'text-lg font-semibold mb-2' }, 'Tabs Navigation'),
// Tab bar
div({ class: 'flex gap-2 border-b border-mist' },
...Object.entries(tabs).map(([key, tab]) =>
button({
onclick: () => activeTab.val = key,
class: () => activeTab.val === key
? 'px-4 py-2 border-b-2 border-sky text-sky font-medium flex items-center gap-2'
: 'px-4 py-2 text-secondary hover:text-midnight flex items-center gap-2'
},
span(tab.icon),
span(tab.label),
tab.count ? Badge({ class: 'subtle vwfcolor-error vwfsize-xs' }, tab.count) : ''
)
)
),
// Tab content
Card({ class: 'p-6 bg-white' },
() => p(tabs[activeTab.val].content)
)
);▶️ Live Preview
Tabs Navigation
This is the overview tab with general information and statistics.
💡 Click "Edit Code" to modify the example
🔄 Click "Apply Changes" to see your modifications
↩️ Click "Reset" to restore original code
Loading States
FeedbackDifferent loading patterns for async operations
// Loading States Pattern
const { div, h3, p } = van.tags;
// Mock profile data
const profile = {
name: 'Jane Doe',
title: 'Software Engineer',
bio: 'Passionate about building great user experiences with modern web technologies.',
stats: { posts: 42, followers: 1234, following: 567 }
};
const loadingProfile = van.state(true);
// Simulate loading
setTimeout(() => {
loadingProfile.val = false;
}, 2000);
// Skeleton component
const ProfileSkeleton = () => Card({ class: 'p-6 bg-white space-y-4' },
div({ class: 'flex gap-4 items-center' },
Skeleton({ class: 'w-16 h-16 rounded-full' }),
div({ class: 'flex-1 space-y-2' },
Skeleton({ class: 'h-4 w-32' }),
Skeleton({ class: 'h-3 w-48' })
)
),
Skeleton({ class: 'h-20' }),
div({ class: 'flex gap-4' },
Skeleton({ class: 'h-12 flex-1' }),
Skeleton({ class: 'h-12 flex-1' }),
Skeleton({ class: 'h-12 flex-1' })
)
);
// Actual profile
const ProfileCard = () => Card({ class: 'p-6 bg-white space-y-4' },
div({ class: 'flex gap-4 items-center' },
Avatar({ name: profile.name, class: 'avtrsize-lg' }),
div({ class: 'flex-1' },
p({ class: 'font-semibold' }, profile.name),
p({ class: 'text-sm text-secondary' }, profile.title)
)
),
p({ class: 'text-sm' }, profile.bio),
div({ class: 'flex gap-4 text-center' },
div({ class: 'flex-1' },
p({ class: 'text-lg font-semibold' }, profile.stats.posts),
p({ class: 'text-xs text-secondary' }, 'Posts')
),
div({ class: 'flex-1' },
p({ class: 'text-lg font-semibold' }, profile.stats.followers),
p({ class: 'text-xs text-secondary' }, 'Followers')
),
div({ class: 'flex-1' },
p({ class: 'text-lg font-semibold' }, profile.stats.following),
p({ class: 'text-xs text-secondary' }, 'Following')
)
)
);
return div(
{ class: 'space-y-4' },
h3({ class: 'text-lg font-semibold mb-2' }, 'Loading States'),
() => loadingProfile.val ? ProfileSkeleton() : ProfileCard()
);▶️ Live Preview
Loading States
💡 Click "Edit Code" to modify the example
🔄 Click "Apply Changes" to see your modifications
↩️ Click "Reset" to restore original code
📋 Best Practices
State Management
• Use individual van.state() for each value
• Keep state close to where it's used
• Update arrays immutably with spread operator
• Use arrow functions for reactive values
Performance
• Debounce expensive operations
• Use keys for list items
• Avoid complex computations in render
• Clean up timers and listeners
Component Design
• Accept both props and children
• Use parseArgs for flexibility
• Provide sensible defaults
• Keep components focused
User Experience
• Show loading states
• Provide immediate feedback
• Handle errors gracefully
• Add smooth transitions