Examples
Real-world examples demonstrating nuclo patterns and best practices.
Counter
The classic counter example showing basic state management and event handling.
import 'nuclo';
let counter = 0;
function increment() {
counter++;
update();
}
function decrement() {
counter--;
update();
}
function reset() {
counter = 0;
update();
}
const app = div(
{ className: 'counter-app' },
h1('Counter: ', span(() => counter)),
div(
{ className: 'button-group' },
button('-', on('click', decrement)),
button('Reset', on('click', reset)),
button('+', on('click', increment))
),
// Show even/odd
p(() => `The counter is ${counter % 2 === 0 ? 'even' : 'odd'}`)
);
render(app, document.body);
Todo List
A complete todo application with add, toggle, delete, and filter functionality.
import 'nuclo';
type Todo = { id: number; text: string; done: boolean };
type Filter = 'all' | 'active' | 'completed';
let todos: Todo[] = [];
let nextId = 1;
let inputValue = '';
let filter: Filter = 'all';
function addTodo() {
if (!inputValue.trim()) return;
todos.push({ id: nextId++, text: inputValue, done: false });
inputValue = '';
update();
}
function toggleTodo(todo: Todo) {
todo.done = !todo.done;
update();
}
function deleteTodo(id: number) {
todos = todos.filter(t => t.id !== id);
update();
}
function clearCompleted() {
todos = todos.filter(t => !t.done);
update();
}
function filteredTodos() {
switch (filter) {
case 'active': return todos.filter(t => !t.done);
case 'completed': return todos.filter(t => t.done);
default: return todos;
}
}
function activeCount() {
return todos.filter(t => !t.done).length;
}
const app = div(
{ className: 'todo-app' },
h1('todos'),
// Input section
div(
{ className: 'input-section' },
input(
{
type: 'text',
placeholder: 'What needs to be done?',
value: () => inputValue
},
on('input', e => {
inputValue = e.target.value;
update();
}),
on('keydown', e => {
if (e.key === 'Enter') addTodo();
})
),
button('Add', on('click', addTodo))
),
// Todo list
when(() => todos.length > 0,
div(
// List
list(() => filteredTodos(), (todo) =>
div(
{ className: () => `todo-item ${todo.done ? 'done' : ''}` },
input(
{
type: 'checkbox',
checked: () => todo.done
},
on('change', () => toggleTodo(todo))
),
span(
{ className: 'todo-text' },
() => todo.text
),
button(
{ className: 'delete-btn' },
'×',
on('click', () => deleteTodo(todo.id))
)
)
),
// Footer
div(
{ className: 'todo-footer' },
span(() => `${activeCount()} item${activeCount() !== 1 ? 's' : ''} left`),
div(
{ className: 'filters' },
button(
{ className: () => filter === 'all' ? 'active' : '' },
'All',
on('click', () => { filter = 'all'; update(); })
),
button(
{ className: () => filter === 'active' ? 'active' : '' },
'Active',
on('click', () => { filter = 'active'; update(); })
),
button(
{ className: () => filter === 'completed' ? 'active' : '' },
'Completed',
on('click', () => { filter = 'completed'; update(); })
)
),
when(() => todos.some(t => t.done),
button('Clear completed', on('click', clearCompleted))
)
)
)
).else(
p({ className: 'empty-state' }, 'No todos yet! Add one above.')
)
);
render(app, document.body);
Search Filter
Real-time search filtering with debouncing.
import 'nuclo';
type User = {
id: number;
name: string;
email: string;
role: string;
};
const users: User[] = [
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' },
{ id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'User' },
{ id: 3, name: 'Charlie Brown', email: 'charlie@example.com', role: 'User' },
{ id: 4, name: 'Diana Prince', email: 'diana@example.com', role: 'Admin' },
{ id: 5, name: 'Eve Anderson', email: 'eve@example.com', role: 'User' }
];
let searchQuery = '';
let selectedRole = 'all';
function filteredUsers() {
const query = searchQuery.toLowerCase();
return users.filter(user => {
const matchesSearch =
user.name.toLowerCase().includes(query) ||
user.email.toLowerCase().includes(query);
const matchesRole =
selectedRole === 'all' || user.role === selectedRole;
return matchesSearch && matchesRole;
});
}
const app = div(
{ className: 'user-directory' },
h1('User Directory'),
// Search and filters
div(
{ className: 'search-section' },
input(
{
type: 'search',
placeholder: 'Search by name or email...',
value: () => searchQuery
},
on('input', e => {
searchQuery = e.target.value;
update();
})
),
select(
{ value: () => selectedRole },
on('change', e => {
selectedRole = e.target.value;
update();
}),
option({ value: 'all' }, 'All Roles'),
option({ value: 'Admin' }, 'Admins'),
option({ value: 'User' }, 'Users')
)
),
// Results count
p(() => {
const count = filteredUsers().length;
return `Showing ${count} user${count !== 1 ? 's' : ''}`;
}),
// User list
when(() => filteredUsers().length > 0,
div(
{ className: 'user-list' },
list(() => filteredUsers(), user =>
div(
{ className: 'user-card' },
h3(user.name),
p(user.email),
span(
{ className: `role-badge ${user.role.toLowerCase()}` },
user.role
)
)
)
)
).else(
div(
{ className: 'empty-state' },
p(() => searchQuery
? `No users found matching "${searchQuery}"`
: 'No users found'
)
)
)
);
render(app, document.body);
Async Loading
Handling asynchronous operations with loading states and error handling.
import 'nuclo';
type Product = { id: number; title: string; category: string; price: number };
type State = {
status: 'idle' | 'loading' | 'success' | 'error';
products: Product[];
error?: string;
};
let state: State = { status: 'idle', products: [] };
let searchQuery = 'phone';
async function fetchProducts() {
if (!searchQuery.trim()) return;
state.status = 'loading';
state.error = undefined;
update();
try {
const response = await fetch(
`https://dummyjson.com/products/search?q=${searchQuery}`
);
if (!response.ok) {
throw new Error('Failed to fetch products');
}
const data = await response.json();
state.products = data.products;
state.status = 'success';
} catch (err) {
state.status = 'error';
state.error = err.message;
}
update();
}
const app = div(
{ className: 'product-search' },
h1('Product Search'),
// Search input
div(
{ className: 'search-bar' },
input(
{
type: 'search',
placeholder: 'Search products...',
value: () => searchQuery,
disabled: () => state.status === 'loading'
},
on('input', e => {
searchQuery = e.target.value;
update();
}),
on('keydown', e => {
if (e.key === 'Enter') fetchProducts();
})
),
button(
{
disabled: () => state.status === 'loading' || !searchQuery.trim()
},
() => state.status === 'loading' ? 'Searching...' : 'Search',
on('click', fetchProducts)
)
),
// Status display
when(() => state.status === 'loading',
div({ className: 'loading' }, 'Loading products...')
).when(() => state.status === 'error',
div(
{ className: 'error' },
'Error: ',
() => state.error,
button('Retry', on('click', fetchProducts))
)
).when(() => state.status === 'success' && state.products.length > 0,
div(
p(() => `Found ${state.products.length} products`),
div(
{ className: 'product-grid' },
list(() => state.products, product =>
div(
{ className: 'product-card' },
h3(product.title),
p({ className: 'category' }, product.category),
p({ className: 'price' }, () => `$${product.price.toFixed(2)}`)
)
)
)
)
).when(() => state.status === 'success' && state.products.length === 0,
div({ className: 'empty' }, () => `No products found for "${searchQuery}"`)
).else(
div({ className: 'empty' }, 'Enter a search term and click Search')
)
);
render(app, document.body);
Form Handling
Complete form with validation and submission.
import 'nuclo';
type FormData = {
username: string;
email: string;
password: string;
confirmPassword: string;
};
type Errors = Partial>;
let formData: FormData = {
username: '',
email: '',
password: '',
confirmPassword: ''
};
let errors: Errors = {};
let isSubmitting = false;
let submitStatus: 'idle' | 'success' | 'error' = 'idle';
function validateForm(): boolean {
errors = {};
if (formData.username.length < 3) {
errors.username = 'Username must be at least 3 characters';
}
if (!formData.email.includes('@')) {
errors.email = 'Please enter a valid email';
}
if (formData.password.length < 6) {
errors.password = 'Password must be at least 6 characters';
}
if (formData.password !== formData.confirmPassword) {
errors.confirmPassword = 'Passwords do not match';
}
return Object.keys(errors).length === 0;
}
async function handleSubmit(e: Event) {
e.preventDefault();
if (!validateForm()) {
update();
return;
}
isSubmitting = true;
submitStatus = 'idle';
update();
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
// Success
submitStatus = 'success';
formData = { username: '', email: '', password: '', confirmPassword: '' };
} catch (err) {
submitStatus = 'error';
}
isSubmitting = false;
update();
}
const app = div(
{ className: 'form-container' },
h1('Sign Up'),
when(() => submitStatus === 'success',
div({ className: 'success-message' }, 'Account created successfully!')
),
form(
on('submit', handleSubmit),
// Username field
div(
{ className: 'form-field' },
label('Username'),
input(
{
type: 'text',
value: () => formData.username,
disabled: () => isSubmitting
},
on('input', e => {
formData.username = e.target.value;
delete errors.username;
update();
})
),
when(() => !!errors.username,
span({ className: 'error' }, () => errors.username)
)
),
// Email field
div(
{ className: 'form-field' },
label('Email'),
input(
{
type: 'email',
value: () => formData.email,
disabled: () => isSubmitting
},
on('input', e => {
formData.email = e.target.value;
delete errors.email;
update();
})
),
when(() => !!errors.email,
span({ className: 'error' }, () => errors.email)
)
),
// Password field
div(
{ className: 'form-field' },
label('Password'),
input(
{
type: 'password',
value: () => formData.password,
disabled: () => isSubmitting
},
on('input', e => {
formData.password = e.target.value;
delete errors.password;
update();
})
),
when(() => !!errors.password,
span({ className: 'error' }, () => errors.password)
)
),
// Confirm password field
div(
{ className: 'form-field' },
label('Confirm Password'),
input(
{
type: 'password',
value: () => formData.confirmPassword,
disabled: () => isSubmitting
},
on('input', e => {
formData.confirmPassword = e.target.value;
delete errors.confirmPassword;
update();
})
),
when(() => !!errors.confirmPassword,
span({ className: 'error' }, () => errors.confirmPassword)
)
),
// Submit button
button(
{
type: 'submit',
disabled: () => isSubmitting
},
() => isSubmitting ? 'Creating account...' : 'Sign Up'
)
)
);
render(app, document.body);
Nested Components
Creating reusable component-like functions.
import 'nuclo';
type User = {
id: number;
name: string;
avatar: string;
bio: string;
followers: number;
};
// Component functions
function UserCard(user: User, onFollow: (id: number) => void) {
return div(
{ className: 'user-card' },
img({ src: user.avatar, alt: user.name, className: 'avatar' }),
h3(user.name),
p({ className: 'bio' }, user.bio),
p({ className: 'followers' }, () => `${user.followers} followers`),
button('Follow', on('click', () => onFollow(user.id)))
);
}
function UserGrid(users: User[], onFollow: (id: number) => void) {
return div(
{ className: 'user-grid' },
list(() => users, user => UserCard(user, onFollow))
);
}
// App state
let users: User[] = [
{
id: 1,
name: 'Alice',
avatar: '/avatars/alice.jpg',
bio: 'Software developer',
followers: 142
},
{
id: 2,
name: 'Bob',
avatar: '/avatars/bob.jpg',
bio: 'Designer',
followers: 89
}
];
function handleFollow(id: number) {
const user = users.find(u => u.id === id);
if (user) {
user.followers++;
update();
}
}
const app = div(
{ className: 'app' },
h1('User Directory'),
UserGrid(users, handleFollow)
);
render(app, document.body);
Animations
Smooth transitions with CSS and reactive styles.
import 'nuclo';
let isVisible = true;
let opacity = 1;
let scale = 1;
function toggle() {
isVisible = !isVisible;
update();
}
function animate() {
let start = Date.now();
function tick() {
const elapsed = Date.now() - start;
const progress = Math.min(elapsed / 1000, 1); // 1 second animation
opacity = isVisible ? progress : 1 - progress;
scale = isVisible ? 0.5 + progress * 0.5 : 1 - progress * 0.5;
update();
if (progress < 1) {
requestAnimationFrame(tick);
}
}
tick();
}
const app = div(
button('Toggle', on('click', () => {
toggle();
animate();
})),
div(
{
className: 'animated-box',
style: {
opacity: () => opacity,
transform: () => `scale(${scale})`,
transition: 'opacity 0.3s, transform 0.3s'
}
},
'Animated Content'
)
);
render(app, document.body);
Simple Routing
Client-side routing without a framework.
import 'nuclo';
type Route = 'home' | 'about' | 'contact' | 'notfound';
let currentRoute: Route = 'home';
let params: Record = {};
function navigate(route: Route) {
currentRoute = route;
window.history.pushState({}, '', `/${route}`);
update();
}
// Simple router
window.addEventListener('popstate', () => {
const path = window.location.pathname.slice(1) || 'home';
currentRoute = path as Route;
update();
});
// Page components
function HomePage() {
return div(
h1('Home Page'),
p('Welcome to our website!'),
button('Go to About', on('click', () => navigate('about')))
);
}
function AboutPage() {
return div(
h1('About Page'),
p('Learn more about us.'),
button('Go to Contact', on('click', () => navigate('contact')))
);
}
function ContactPage() {
return div(
h1('Contact Page'),
p('Get in touch!'),
button('Go Home', on('click', () => navigate('home')))
);
}
function NotFoundPage() {
return div(
h1('404 - Not Found'),
p('Page not found'),
button('Go Home', on('click', () => navigate('home')))
);
}
const app = div(
{ className: 'app' },
nav(
button('Home', on('click', () => navigate('home'))),
button('About', on('click', () => navigate('about'))),
button('Contact', on('click', () => navigate('contact')))
),
main(
when(() => currentRoute === 'home', HomePage())
.when(() => currentRoute === 'about', AboutPage())
.when(() => currentRoute === 'contact', ContactPage())
.else(NotFoundPage())
)
);
render(app, document.body);
More Examples
Find more examples in the GitHub repository.