vwf .

Core VanJS APIs

VanJS core APIs for building reactive applications

⚡ VanJS: Ultra-Lightweight Framework

Just 1.5KB of pure JavaScript - 100x smaller than React, yet powerful enough for production apps.

1.5KB

Minified + Gzip

130

Lines of Code

0

Dependencies

100x

Smaller Than React

🔄van.state()

Reactive state management - the heart of VanJS. Changes automatically trigger DOM updates.

Basic Usage

import van from 'vanjs-core';

// Create reactive state
const count = van.state(0);

// Read value
console.log(count.val);  // → 0

// Update value
count.val = 5;           // DOM automatically updates!

// Update with current value
count.val = count.val + 1;
// or
count.val++;

Reactive Rendering - Two Patterns

const count = van.state(0);

// Pattern 1: Direct state (re-renders on change)
div('Count: ', count);
// Updates automatically when count.val changes

// Pattern 2: Arrow function (computed/conditional)
div(() => `Count is: ${count.val}`);
div(() => count.val > 10 ? 'High' : 'Low');
div(() => count.val % 2 === 0 ? 'Even' : 'Odd');

// ❌ WRONG - Not reactive!
const isEven = count.val % 2 === 0;
div(isEven ? 'Even' : 'Odd');
// This evaluates once and never updates

Working with Arrays and Objects

// Arrays - use immutable updates
const items = van.state([1, 2, 3]);

// ✅ CORRECT - Create new array
items.val = [...items.val, 4];
items.val = items.val.filter(x => x !== 2);
items.val = items.val.map(x => x * 2);

// ❌ WRONG - Mutates array, won't trigger update
items.val.push(4);  // Doesn't work!
items.val[0] = 10;  // Doesn't work!

// Objects - use immutable updates
const user = van.state({ name: 'John', age: 30 });

// ✅ CORRECT - Create new object
user.val = { ...user.val, age: 31 };
user.val = { ...user.val, email: 'john@example.com' };

// ❌ WRONG - Mutates object, won't trigger update
user.val.age = 31;  // Doesn't work!

Multiple State Values

// ✅ RECOMMENDED - Individual states
const firstName = van.state('');
const lastName = van.state('');
const email = van.state('');
const age = van.state(0);

// Easy to update independently
firstName.val = 'John';
email.val = 'john@example.com';

// ⚠️ Possible but not recommended - Single object state
const form = van.state({
  firstName: '',
  lastName: '',
  email: '',
  age: 0
});

// Requires spread on every update
form.val = { ...form.val, firstName: 'John' };

📊van.derive()

Create computed values that automatically update when dependencies change.

Basic Derived State

const count = van.state(0);

// Derived value - automatically updates
const doubled = van.derive(() => count.val * 2);
const isEven = van.derive(() => count.val % 2 === 0);

console.log(doubled.val);  // → 0
count.val = 5;
console.log(doubled.val);  // → 10
console.log(isEven.val);   // → false

Multiple Dependencies

const firstName = van.state('John');
const lastName = van.state('Doe');

// Derived from multiple states
const fullName = van.derive(
  () => `${firstName.val} ${lastName.val}`
);

console.log(fullName.val);  // → 'John Doe'
firstName.val = 'Jane';
console.log(fullName.val);  // → 'Jane Doe'

When to Use van.derive() vs Arrow Functions

  • • Use van.derive() for a reusable computed state
  • • Use van.derive() when the computation is expensive
  • • Use arrow functions for inline reactive rendering
  • • Use arrow functions for simple conditional display
  • • Arrow functions: () => count.val > 10 ? "High" : "Low"
  • • Derived state: const status = van.derive(() => count.val > 10 ? "High" : "Low")

🏷️van.tags

Create HTML elements using native JavaScript - no JSX, no templates, just functions.

Destructuring Tags

import van from 'vanjs-core';

// Destructure only the tags you need
const { div, h1, h2, p, span, button, input, form } = van.tags;

// All HTML tags available
const { a, ul, li, table, tr, td, img, svg } = van.tags;

Creating Elements

// Basic element
div('Hello World');

// With attributes (props object first)
div({ class: 'container' }, 'Content');

// Multiple children
div(
  { class: 'card' },
  h1('Title'),
  p('Description'),
  button('Click Me')
);

// Nested elements
div({ class: 'page' },
  div({ class: 'header' },
    h1('My App')
  ),
  div({ class: 'content' },
    p('Welcome to the app')
  )
);

Event Handlers

const count = van.state(0);

button({
  onclick: () => count.val++,
  class: 'btn btn-primary'
}, 'Increment');

input({
  type: 'text',
  oninput: (e) => email.val = e.target.value,
  placeholder: 'Enter email'
});

form({
  onsubmit: (e) => {
    e.preventDefault();
    handleSubmit();
  }
}, /* form content */);

Attribute Patterns

// Static attributes
div({ id: 'app', class: 'container' });

// Reactive attributes
const isActive = van.state(false);
div({ class: () => isActive.val ? 'active' : '' });

// Multiple reactive classes
div({
  class: () => `base-class ${isActive.val ? 'active' : ''}`
});

// Style attribute (must be string!)
div({ style: 'color: red; font-size: 16px' });

// Reactive style
div({
  style: () => `width: ${progress.val}%; background: blue`
});

// Data attributes
div({ 'data-id': '123', 'data-type': 'card' });

⨁ van.add()

Dynamically add child elements to existing DOM nodes after initial render.

Basic Usage

// Create a container
const container = div({ class: 'messages' });

// Add elements later
van.add(container, p('First message'));
van.add(container, p('Second message'));

// Add multiple elements
van.add(container,
  p('Message 3'),
  p('Message 4'),
  p('Message 5')
);

Dynamic List Example

const items = van.state([]);
const listContainer = ul({ class: 'todo-list' });

const addItem = (text) => {
  items.val = [...items.val, { id: Date.now(), text }];
  van.add(listContainer,
    li({ 'data-id': Date.now() }, text)
  );
};

// Note: For reactive lists, use arrow functions instead:
const ReactiveLis  t = () => {
  return ul(
    { class: 'todo-list' },
    () => items.val.map(item =>
      li({ key: item.id }, item.text)
    )
  );
};

When to Use van.add()

  • ✅Adding elements to existing containers imperatively
  • ✅Building UI progressively (chat msgs, notifications)
  • ✅Third-party library integration
  • ⚠️Prefer arrow func with .map() for reactive lists
  • ❌Don't use if declarative reactive rendering works

🎯vwfPattern Extensions

vwfadds standardized patterns on top of VanJS for consistency and developer experience.

Message Pattern (Validation Feedback)

// Standard message format
const emailMsg = van.state({});

// Message structure
{
  code: 'error' | 'warning' | 'success' | 'info',
  detail: 'Human readable message'
}

// Usage with Input component
Input({
  label: 'Email',
  value: email,
  message: emailMsg,  // Reactive feedback
  oninput: (e) => {
    email.val = e.target.value;
    emailMsg.val = validateEmail(e.target.value);
  }
});

// Validator returns message
const validateEmail = (value) => {
  if (!value) return { code: 'error', detail: 'Required' };
  if (!value.includes('@')) return { code: 'error', detail: 'Invalid email' };
  return { code: 'success', detail: 'Valid!' };
};

CSS Variable Utilities Pattern

// Standardized utility classes
Button({ class: 'vwfcolor-sky solid vwfsize-lg' });
Badge({ class: 'vwfcolor-success subtle vwfsize-sm' });
Avatar({ class: 'vwfcolor-earth solid vwfsize-md' });

// Components use CSS variables internally
// vwfcolor-sky → sets --vwf-color CSS variable
// Component uses: background: --vwf-color

// Override with Tailwind classes
Button({ class: 'vwfcolor-sky solid bg-purple-500' });
// filterClassName() removes conflicts smartly

Component Flexibility Pattern

// All these calling patterns work
Button();
Button('Click Me');
Button({ variant: 'primary' });
Button({ variant: 'primary' }, 'Click Me');
Button({ variant: 'primary' }, Icon(), 'Click Me');

// Thanks to parseArgs() utility
export const Button = (...args) => {
  const [props, children] = parseArgs(args);
  // Handle all patterns uniformly
};

⚠️Critical Reactivity Rules

✅DO

  • • Use arrow functions for computed values
  • • Update arrays/objects immutably
  • • Use van.derive() for expensive computations
  • • Pass state directly for simple display
  • • Use individual states, not one big object
  • • Clean up in component.preUnmount hooks

❌DON'T

  • ❌Mutate arrays: arr.push(), arr[0] = x
  • ❌Mutate objects: obj.prop = value
  • ❌Extract .val outside reactive context
  • ❌Use complex expressions in render
  • ❌Forget to spread when updating state
  • ❌Use state.val++ in async callbacks

Conditional Rendering Rule

// ✅ CORRECT - Use ternary
() => show.val ? div('Content') : ''

// ❌ WRONG - Renders "false" as text
() => show.val && div('Content')

Style Attribute Rule

// ✅ CORRECT - String
style: 'width: 100px'
style: () => `width: ${w.val}px`

// ❌ WRONG - Object format
style: { width: '100px' }

📋Quick Reference

Core API

  • • van.state(initial) - Create state
  • • van.derive(fn) - Computed state
  • • van.tags - HTML elements
  • • van.add(parent, ...children) - Dynamic add
  • • state.val - Read/write value
  • • state.oldVal - Previous value

VWF Extensions

  • • parseArgs() - Flexible components
  • • cx() - Class name joiner
  • • filterClassName() - Conflict resolution
  • • val() - Value extractor
  • • Message pattern - Validation
  • • vwfcolor-* utilities

Best Practices

  • • Keep state close to usage
  • • Immutable updates always
  • • Arrow functions for reactivity
  • • Clean up resources
  • • Use ternary for conditionals
  • • Individual states over objects

🔗 Learn More

Explore vwf

Discover what vwf can do