Overview • Features • Architecture • Get Started • API • Demo
A lightweight, zero-dependency JavaScript framework for building reactive web apps — designed to stay small, readable, and easy to learn from.
Jump to any section below — the framework is small, but the docs are detailed so it’s easy to understand and extend.
- Overview
- Features
- Tech Stack
- Architecture
- Framework Structure
- Getting Started
- API Documentation
- Code Examples
- TodoMVC Demo
- Why It Works This Way
- Contributing
- License
- Zero-dependency by design — no external libraries required.
- Reactive state + Virtual DOM — UI updates automatically with minimal DOM changes.
- Event delegation system — performance-friendly handlers with cleanup.
- Hash-based routing — simple navigation that syncs with state.
- Educational architecture — modular, readable code meant to be studied and extended.
State (store) → view(state) → Virtual DOM tree
↓
diff + patch
↓
Real DOM
PicoJS Framework is a small JavaScript framework built to show how modern UI ideas fit together while you build reactive web apps. It borrows familiar patterns from frameworks like React and Vue but keeps Virtual DOM rendering, reactive state management, event delegation, and hash-based routing in one place with zero external dependencies, staying under 10KB gzipped.
The code is written to be read: you can trace how state drives the UI without sifting through layers of abstraction, whether you're shipping a small project or studying framework internals.
- Virtual DOM - Calculates diffs and patches only the DOM nodes that change
- Reactive State - State updates automatically trigger re-renders
- Component Architecture - Compose UIs from plain functions that return virtual nodes
- Event System - Attach events declaratively; delegation handles cleanup
- Router - Hash-based navigation that stays in sync with application state
- Zero Dependencies - Runs without pulling in external libraries
- Small Bundle Size - Under 10KB gzipped to keep downloads small
- ES6 Modules - Uses native modules so bundlers can tree-shake if needed
- Educational - Organized for reading, tinkering, and modifying
- Browser Native - Works in all modern browsers without transpilation
- Efficient Diffing - Compares virtual trees and updates only changed nodes
- Event Delegation - Shares listeners on the root instead of per element
- Memory Efficient - Cleans up listeners when elements leave the DOM
- Fast Rendering - Lean Virtual DOM patching keeps updates responsive
- JavaScript ES6+ - Uses native modules and modern syntax
- Virtual DOM - Custom implementation to control render work
- Hash Routing - Client-side routes via URL hashes without extra server setup
- Event Delegation - Centralized handling to reduce attached listeners
- Chrome 60+
- Firefox 55+
- Safari 11+
- Edge 79+
- Local Server - Uses Python's built-in server to serve files locally
- ES6 Modules - Relies on native browser module support; bundlers optional
- Git - Version control for changes and collaboration
PicoJS keeps modules small and focused so it's clear how each part connects to the next. The framework leans on three ideas:
- State Management - A reactive store that triggers re-renders whenever data changes
- Virtual DOM - A lightweight representation of the real DOM to plan updates
- Event System - Declarative event handling with delegation to avoid scattered listeners
A user interaction moves through a handler, updates state, and re-runs the view. The view returns a Virtual DOM tree that's diffed against the previous one so only the necessary changes reach the real DOM.
User Interaction → Event Handler → State Update → View Function → Virtual DOM → DOM Patch → UI Update
This one-way path keeps applications predictable and easier to debug.
The framework is organized into focused modules, each handling a specific concern:
picojs-framework/
├── framework/
│ ├── core.js # Main exports and framework initialization
│ ├── app.js # Application lifecycle management
│ ├── store.js # Reactive state management
│ ├── router.js # Hash-based routing system
│ ├── events.js # Event delegation and handling
│ └── vdom/ # Virtual DOM implementation
│ ├── createElement.js # Virtual node creation utilities
│ ├── render.js # Main rendering pipeline
│ ├── patch.js # DOM diffing and patching
│ ├── attrs.js # Attribute management
│ └── domElement.js # DOM element creation
├── app/ # Application code
│ ├── main.js # Main application entry point
│ └── styles.css # Application styles
├── index.html # Main HTML page
├── createApp.sh # Project scaffolding script
└── README.md # This documentation
graph TD
A[core.js] --> B[app.js]
A --> C[store.js]
A --> D[router.js]
A --> E[events.js]
A --> F[vdom/]
B --> C
B --> F
F --> G[createElement.js]
F --> H[render.js]
F --> I[patch.js]
F --> J[attrs.js]
F --> K[domElement.js]
D --> C
E --> F
L[main.js] --> A
L --> M[index.html]
style A fill:#e1f5fe
style F fill:#f3e5f5
style L fill:#e8f5e8
Core Module (core.js) - The main entry point that exports all framework functionality App Module (app.js) - Handles application initialization and the main render loop Store Module (store.js) - Manages reactive state with subscription system Router Module (router.js) - Provides hash-based navigation Events Module (events.js) - Implements event delegation for performance VDOM Modules - Handle virtual DOM creation, diffing, and patching
The fastest way to get started is using the included setup script:
# Clone or download the framework
git clone https://github.com/sahmedhusain/picojs-framework.git
cd picojs-framework
# Create a new project
./createApp.sh my-awesome-app
# Move into your new project
cd my-awesome-app
# Start development server
python3 -m http.server 8000
# Open in browser
open http://localhost:8000The script creates a complete project with:
- All framework files copied
- A starter application with counter example
- Proper directory structure
- Ready-to-run HTML file
If you prefer to set up manually:
- Create your HTML file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My PicoJS App</title>
<link rel="stylesheet" href="/app/styles.css">
</head>
<body>
<div id="root"></div>
<script src="/app/main.js" type="module"></script>
</body>
</html>- Create your main JavaScript file (
/app/main.js):
import { createApp, createElement as h } from '../framework/core.js';
// Your app code here
const initialState = { count: 0 };
function view(state) {
return h('div', {},
h('h1', {}, `Count: ${state.count}`),
h('button', {
onclick: () => store.setState({ count: state.count + 1 })
}, 'Increment')
);
}
const store = createApp({
view,
initialState,
rootElement: document.getElementById('root')
});- Start a local server:
python3 -m http.server 8000📚 API Documentation (expand/collapse)
Creates a virtual DOM element. This is the foundation of building UIs in PicoJS.
Parameters:
tag(string) - HTML tag name ('div', 'span', 'button', etc.)attrs(object) - Attributes and event handlerschildren(...any) - Child elements or text content
Returns: Virtual node object
Examples:
import { createElement as h } from './framework/core.js';
// Simple text element
const title = h('h1', {}, 'Hello World');
// Element with attributes
const input = h('input', {
type: 'text',
placeholder: 'Enter your name',
value: 'John Doe'
});
// Nested elements
const card = h('div', { class: 'card' },
h('h2', {}, 'Card Title'),
h('p', {}, 'Card content here...')
);Initializes your PicoJS application with state management and rendering.
Parameters:
options.view(function) - Function that returns virtual DOM based on stateoptions.initialState(object) - Starting state for your applicationoptions.rootElement(HTMLElement) - DOM element to render into
Returns: Store object with state management methods
Example:
import { createApp, createElement as h } from './framework/core.js';
const initialState = {
todos: [],
filter: 'all'
};
function view(state) {
return h('div', {},
// Your UI components here
);
}
const store = createApp({
view,
initialState,
rootElement: document.getElementById('root')
});Creates a reactive state store. Usually used internally by createApp.
Methods:
getState()- Returns current statesetState(newState)- Updates state and triggers re-rendersubscribe(listener)- Adds function to call on state changes
Example:
import { createStore } from './framework/core.js';
const store = createStore({ count: 0 });
store.subscribe(() => {
console.log('State updated:', store.getState());
});
store.setState({ count: 1 }); // Triggers subscriptionAdds hash-based routing that syncs with your application state.
Parameters:
store(object) - Your app's store fromcreateApp
Example:
import { createApp, createElement as h } from './framework/core.js';
import { createRouter } from './framework/router.js';
const initialState = { route: '#/' };
function view(state) {
return h('div', {},
h('nav', {},
h('a', { href: '#/home' }, 'Home'),
h('a', { href: '#/about' }, 'About')
),
h('p', {}, `Current route: ${state.route}`)
);
}
const store = createApp({ view, initialState, rootElement: document.getElementById('root') });
createRouter(store); // Now URL changes update state.route💡 Code Examples (expand/collapse)
import { createApp, createElement as h } from '../framework/core.js';
const initialState = { count: 0 };
function view(state) {
return h('div', { class: 'counter' },
h('h1', {}, `Count: ${state.count}`),
h('button', {
onclick: () => store.setState({ count: state.count + 1 })
}, '+'),
h('button', {
onclick: () => store.setState({ count: state.count - 1 })
}, '-')
);
}
const store = createApp({
view,
initialState,
rootElement: document.getElementById('root')
});import { createApp, createElement as h } from '../framework/core.js';
const initialState = {
todos: [],
inputValue: ''
};
function view(state) {
return h('div', { class: 'todo-app' },
h('h1', {}, 'Todo List'),
h('input', {
type: 'text',
value: state.inputValue,
placeholder: 'Add a new todo...',
oninput: (e) => store.setState({ inputValue: e.target.value })
}),
h('button', {
onclick: () => {
if (state.inputValue.trim()) {
store.setState({
todos: [...state.todos, {
id: Date.now(),
text: state.inputValue,
completed: false
}],
inputValue: ''
});
}
}
}, 'Add Todo'),
h('ul', {},
...state.todos.map(todo =>
h('li', {
class: todo.completed ? 'completed' : '',
onclick: () => {
const updatedTodos = state.todos.map(t =>
t.id === todo.id ? { ...t, completed: !t.completed } : t
);
store.setState({ todos: updatedTodos });
}
}, todo.text)
)
)
);
}
const store = createApp({
view,
initialState,
rootElement: document.getElementById('root')
});import { createApp, createElement as h } from '../framework/core.js';
import { createRouter } from '../framework/router.js';
const initialState = { route: '#/' };
function view(state) {
const routes = {
'#/': h('div', {}, h('h1', {}, 'Home Page'), h('p', {}, 'Welcome!')),
'#/about': h('div', {}, h('h1', {}, 'About'), h('p', {}, 'About this app...')),
'#/contact': h('div', {}, h('h1', {}, 'Contact'), h('p', {}, 'Get in touch!'))
};
return h('div', {},
h('nav', {},
h('a', { href: '#/home' }, 'Home'),
h('a', { href: '#/about' }, 'About'),
h('a', { href: '#/contact' }, 'Contact')
),
routes[state.route] || h('div', {}, h('h1', {}, '404 - Page Not Found'))
);
}
const store = createApp({ view, initialState, rootElement: document.getElementById('root') });
createRouter(store);The framework includes a complete TodoMVC implementation that demonstrates all features in action. This is the classic TodoMVC example that developers use to compare framework capabilities.
Features demonstrated:
- Adding, editing, and deleting todos
- Marking todos as complete/incomplete
- Filtering todos (All, Active, Completed)
- Bulk actions (toggle all, clear completed)
- Local storage persistence
- Responsive design
To run the demo:
- Open
index.htmlin your browser - Start adding todos and explore the features
- Check the browser's developer tools to see the Virtual DOM in action
The TodoMVC code serves as an excellent reference for building real applications with PicoJS.
Instead of directly manipulating the DOM (which can be slow and error-prone), PicoJS uses a Virtual DOM:
- Describe your UI as a function of state
- Generate virtual elements (plain JavaScript objects)
- Compare old vs new virtual trees
- Update only what changed in the real DOM
This approach gives you:
- Performance - Minimal DOM operations
- Predictability - UI is always in sync with state
- Simplicity - No manual DOM manipulation
State changes automatically trigger UI updates through a subscription system:
// When you call setState
store.setState({ count: 1 });
// The framework automatically:
// 1. Updates internal state
// 2. Calls your view function with new state
// 3. Generates new virtual DOM
// 4. Patches the real DOM
// 5. UI updates instantlyInstead of attaching event listeners to every element, PicoJS uses event delegation:
- Single listener per event type on the root element
- Event bubbling carries events up to the root
- Data attributes identify which element was clicked
- Automatic cleanup when elements are removed
This provides better performance and prevents memory leaks.
Hash routing (#/page) works without server configuration:
- No page reloads - Changes happen instantly
- Bookmarkable - URLs work with browser back/forward
- Simple - No complex server setup needed
- State synced - URL changes update your app state
Check out the live demo deployed on GitHub Pages:
https://sahmedhusain.github.io/picojs-framework
The demo includes the full TodoMVC application running directly in the browser.
We welcome contributions! Here's how you can help:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Test thoroughly - Make sure existing functionality still works
- Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Keep the bundle size small
- Maintain zero dependencies
- Write clear, commented code
- Add examples for new features
- Test in multiple browsers
This project is licensed under the MIT License - see the LICENSE.md file for details.
Sayed Ahmed Husain
Email: [email protected]
Built with ❤️ for simplicity