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 updatesWorking 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); // → falseMultiple 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 smartlyComponent 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