🚀 Ultra-Compact Core
Complete reactive system, routing and components in under 9kb. No bloatware or hidden dependencies.
Build modern web applications with a minimalist approach: full framework power in just 9kb
Complete reactive system, routing and components in under 9kb. No bloatware or hidden dependencies.
Granular update system that only modifies what's needed in the DOM using native Proxy.
Each component manages its own reactive state, lifecycle and scoped template.
Bind data with simple HTML attributes (data-bind
, data-model
) without JSX.
SPA navigation system with under 200 bytes of core code. Simple yet powerful.
Runs immediately in browser with no build steps. Perfect for rapid prototyping.
"Do one thing well" principle. Concise and predictable methods.
Start with a traditional script and scale to full PWA as needed.
Use only what you need and integrate with any existing stack.
Just include the script in your HTML file:
<script src="lila.js"></script>
<script src="https://seip25.github.io/Lila_js/lila.js"></script>
Here's a minimal setup for a Lila JS application:
<body>
<nav>
<a href="/" data-link>Home</a>
<a href="/about" data-link>About</a>
</nav>
<main id="app-lila"></main>
<template data-template="home-template">
<h1>Welcome</h1>
<p data-bind="message"></p>
</template>
<template data-template="about-template">
<h1>About</h1>
<p>This is the about us page.</p>
</template>
<script>
App.createComponent('Home', {
template: 'home-template',
state: () => ({ message: 'Hello World!' })
});
App.addRoute('/', 'Home');
App.createComponent('About', {
template: 'about-template'
});
App.addRoute('/about', 'About');
handleRouting();
</script>
</body>
A simple counter with increment, decrement and reset functionality, plus auto-increment every second.
<template data-template="counter-template">
<h1>Counter</h1>
<p>Current count: <span data-bind="count"></span></p>
<button data-action="increment">Increment</button>
<button data-action="decrement">Decrement</button>
<button data-action="reset">Reset</button>
</template>
const incrementCounter = ({ state }) => { state.count++ };
App.createComponent('Counter', {
template: 'counter-template',
state: () => ({
count: 0,
interval: null
}),
actions: {
increment: incrementCounter,
decrement: ({ state }) => { state.count-- },
reset: ({ state }) => { state.count = 0 }
},
onMount: (state) => {
console.log('Counter mounted');
state.interval = setInterval(() => {
console.log('Interval tick', state.count);
state.count++;
}, 1000);
},
onDestroy: (state) => {
console.log('Counter destroyed');
if (state.interval) {
clearInterval(state.interval);
state.interval = null;
}
}
});
App.addRoute('/counter', 'Counter');
onMount
starts auto-incrementing,
onDestroy
cleans up
/counter
routeA simple static page component.
<template data-template="about-template">
<h1>About</h1>
<p>This is the about us page.</p>
</template>
App.createComponent('About', {
template: 'about-template'
});
App.addRoute('/about', 'About');
A fallback component for unmatched routes with a button to return home.
<template data-template="not-found-template">
<h1>404 Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
<button data-action="goHome">Go Home</button>
</template>
App.createComponent('NotFound', {
template: 'not-found-template',
actions: {
goHome: () => {
App.navigateTo('#/');
}
}
});
App.addRoute('*', 'NotFound');
*
)goHome
action redirects to rootA complete home page with form handling, data binding, and nested components.
<template data-template="home-template">
<h1>¡Bienvenido a la página de inicio!</h1>
<form data-action="submit">
<input type="text" name="greet" data-bind="greet" placeholder="Ingresa tu saludo" class="w-full" />
<button type="submit" class="contrast">
<i class="icon-send"></i>Enviar
</button>
</form>
<br />
<div>
<p>Tu saludo es: <span data-bind="greet"></span></p>
</div>
<div class="input-icon">
<i class="icon-edit"></i>
<input type="text" data-model="greet" placeholder="Cambiar saludo" value="">
</div>
<button data-action="changeGreeting" class="w-full">
<i class="icon-check-circle"></i>Cambiar Saludo
</button>
<br />
<div data-component="Counter"></div>
</template>
App.createComponent('Home', {
template: 'home-template',
state: () => ({
greet: null
}),
actions: {
changeGreeting: ({ state }) => {
state.greet = state.greet === "Hello World!"
? "Hola Mundo!"
: "Hello World!";
console.log(state.greet);
},
submit: ({ state, event }) => {
event.preventDefault();
const formData = Object.fromEntries(new FormData(event.target));
console.log('Form submitted:', formData);
alert(JSON.stringify(formData));
}
}
});
App.addRoute('/', 'Home');
The data-action="submit"
attribute binds the form submission to the component's
submit action.
data-model="greet"
creates two-way binding between the input and state property.
data-bind="greet"
displays the state value and updates when it changes.
data-component="Counter"
embeds the Counter component within the Home component.
data-action="changeGreeting"
binds the button click to the component action.
The submit action uses FormData
to easily process form inputs.
This example shows how all components work together in a complete application.
<header class="container">
<nav>
<a href="/" data-link>Inicio</a>
<a href="/about" data-link>Acerca de</a>
<a href="/counter" data-link>Contador</a>
</nav>
</header>
<main id="app-lila" class="container"></main>
<footer></footer>
<!-- All templates would be included here -->
// Define all components first
App.createComponent('Home', { /* ... */ });
App.createComponent('About', { /* ... */ });
App.createComponent('Counter', { /* ... */ });
App.createComponent('NotFound', { /* ... */ });
// Set up routes
App.addRoute('/', 'Home');
App.addRoute('/about', 'About');
App.addRoute('/counter', 'Counter');
App.addRoute('*', 'NotFound'); // Catch-all route
// Initialize routing
handleRouting();
Show or hide elements based on conditions in your component state.
<template data-template="about-template">
<h1>Acerca de</h1>
<div data-if="items.length === 0">
<p>No hay items.</p>
</div>
<div data-if="items.length > 3">
<p>Hay más de 3 items.</p>
</div>
<div data-if="items.length > 0">
<span data-bind="items"></span>
</div>
</template>
App.createComponent('About', {
template: 'about-template',
state: () => ({
items: []
}),
actions: {
addItem: ({ state }) => {
state.items = [...state.items, `Item ${state.items.length + 1}`];
},
removeItem: ({ state }) => {
state.items.pop();
}
},
onMount: (state) => {
state.items = ['Item 1', 'Item 2', 'Item 3'];
}
});
display: none
to hide elements (preserves DOM position)Directive | Description | Example |
---|---|---|
data-if |
Conditionally shows/hides element based on expression | <div data-if="items.length > 0">...</div> |
data-bind |
Binds element content to state property | <span data-bind="count"></span> |
data-action |
Binds DOM events to component actions | <button data-action="increment"></button> |
data-link |
Router link navigation | <a href="/about" data-link></a> |
Use simple boolean expressions in data-if
for better performance.
Recommended: data-if="isActive"
Avoid: data-if="user.roles.includes('admin') && items.length > 5"
Use data-bind
inside conditional blocks to display dynamic content.
<div data-if="hasItems">
<span data-bind="itemCount"></span> items
</div>
For toggle between views, use complementary conditions.
<div data-if="isEditing">Edit Mode</div>
<div data-if="!isEditing">View Mode</div>
Render dynamic lists from your component state with automatic updates.
<template data-template="about-template">
<h1>Acerca de</h1>
<div class="mt-4" data-if="items.length === 0">
<p>No hay items.</p>
</div>
<div data-repeat="items">
<article class="mt-4 flex between gap-2" data-repeated-item="" secondary>
<span class="flex between gap-1">
<span>Index:</span>
<span data-repeat-bind="index"></span>
</span>
<span data-repeat-bind="item.id"></span>
<span data-repeat-bind="item.name"></span>
<button data-repeat-action="removeItemId" class="btn-error">
<i class="icon-delete"></i>
<span data-repeat-bind="'Remove ' + item.name"></span>
</button>
</article>
</div>
</template>
App.createComponent('About', {
template: 'about-template',
state: () => ({
items: []
}),
actions: {
addItem: ({ state }) => {
let item = {
name: 'Item ' + (state.items.length + 1),
id: state.items.length + 1
};
state.items = [...state.items, item];
},
removeItem: ({ state }, index) => {
let items = state.items;
items.splice(index, 1);
state.items = [...items];
},
removeItemId: ({ state, event, item, index }) => {
event.preventDefault();
state.items = state.items.filter((i) => i.id !== item.id);
}
},
onMount: (state) => {
state.items = [
{ name: 'Item 1', id: 1 },
{ name: 'Item 2', id: 2 },
{ name: 'Item 3', id: 3 }
];
}
});
item
and index with index
Directive | Description | Example |
---|---|---|
data-repeat |
Repeats element for each item in array | <div data-repeat="items">...</div> |
data-repeat-bind |
Binds content to current item properties | <span data-repeat-bind="item.name"></span> |
data-repeat-action |
Binds action with item context | <button data-repeat-action="removeItem"></button> |
data-repeated-item |
Marker attribute for repeated items (auto-added) | <div data-repeated-item></div> |
Include unique identifiers in your items for better performance with dynamic lists.
state.items = [
{ id: 1, name: 'Item 1' }, // Good - has unique id
{ id: 2, name: 'Item 2' }
];
Use data-if
to show empty state messages.
<div data-if="items.length === 0">
<p>No items found</p>
</div>
<div data-repeat="items">...</div>
Check for property existence when binding to avoid errors.
<span data-repeat-bind="item.name || 'Unnamed'"></span>
Variable | Available In | Description |
---|---|---|
item |
data-repeat-bind , data-repeat-action |
Current item in the iteration |
index |
data-repeat-bind , data-repeat-action |
Current index (0-based) in the iteration |
When using data-repeat-action
in repeated items, your action handler receives additional context properties:
// Action signature for repeated items
actions: {
handleItem: ({
state, // Component state
event, // Original DOM event
item, // Current item object from the array
index // Current index in the array
}) => {
// Action logic here
}
}
Property | Type | Description | Example Usage |
---|---|---|---|
state |
Object | Component state object (read/write) | state.items.push(newItem) |
event |
DOM Event | Original event that triggered the action | event.preventDefault() |
item |
Object | Current item from the repeated array | console.log(item.id) |
index |
Number | Current index in the array (0-based) | state.items.splice(index, 1) |
removeItem: ({ state, item }) => {
state.items = state.items.filter(i => i.id !== item.id);
}
updateItem: ({ state, item, index }) => {
state.items[index] = { ...item, updated: true };
state.items = [...state.items]; // Trigger reactivity
}
handleClick: ({ event, item }) => {
event.stopPropagation();
console.log('Clicked item:', item.name);
}
event.preventDefault()
for form submissions or anchor clicks
// Good - triggers update
state.items = state.items.filter(...);
// Bad - won't trigger reactive update
state.items.splice(index, 1);
item
reference is the same object as in the array (not a deep clone)
// Create a reactive state object
const state = App.reactive({
count: 0
});
// Create a component
App.createComponent('ComponentName', {
template: 'template-id', // ID of the template element
state: () => ({ ... }), // Initial state function
actions: { // Component methods
actionName: ({ state, event }, ...args) => { ... }
},
onMount: (state) => { // Called when component mounts
console.log('Mounted');
},
onDestroy: (state) => { // Called when component unmounts
console.log('Destroyed');
}
});
// Routing
App.addRoute('/path', 'ComponentName');
App.navigateTo('/path'); // Programmatic navigation
handleRouting();//Execute routes
Directive | Description | Example |
---|---|---|
data-bind |
One-way data binding | <span data-bind="message"></span> |
data-model |
Two-way data binding for form inputs | <input data-model="username"> |
data-action |
Bind component method to DOM events | <button data-action="submit"></button> |
data-component |
Nested components | <div data-component="Child"></div> |
data-link |
Router link | <a href="/about" data-link></a> |
data-if |
Conditional | <div data-if="items.length >0"></div> |