vwf .

Production Patterns

Production-ready patterns with live, editable code examples

Reference/Patterns/Examples/All Categories

Form with Validation

Forms

Real-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

State

Add, 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

Total items: 3Filtered: 3
Apple
Banana
Orange

💡 Click "Edit Code" to modify the example

🔄 Click "Apply Changes" to see your modifications

↩️ Click "Reset" to restore original code

Polling & Auto-Refresh

Performance

Automatically 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

● Live

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

Components

Reusable 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

Navigation

Tab 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

Feedback

Different 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