Skip to the content.

Getting Started

Installation

Getting started with Lips is as simple as including the script in your HTML file. Since Lips is a runtime framework with no build step required, you can start developing immediately.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Lips App</title>
  <script src="https://cdn.jsdelivr.net/npm/@lipsjs/lips"></script>
</head>
<body>
  <div id="app"></div>
  <script>
    // Your Lips code here
  </script>
</body>
</html>

Using npm (Optional)

If you prefer using npm for dependency management:

npm install @lipsjs/lips

Then import it into your project:

import Lips from '@lipsjs/lips';

Quick Start Example

Let’s create a simple counter component to get a feel for how Lips works:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Lips Counter Example</title>
  <script src="https://cdn.jsdelivr.net/npm/@lipsjs/lips"></script>
</head>
<body>
  <div id="app"></div>
  
  <script>
    // Initialize Lips with debug mode
    const lips = new Lips({ debug: true });
    
    // Define a counter component
    const counterTemplate = {
      // Component state
      state: {
        count: 0
      },
      
      // Event handlers
      handler: {
        increment() {
          this.state.count++;
        },
        
        decrement() {
          if (this.state.count > 0) {
            this.state.count--;
          }
        }
      },
      
      // Component template
      default: `
        <div class="counter">
          <h2>Counter: {state.count}</h2>
          <button on-click(increment)>+</button>
          <button on-click(decrement)>-</button>
        </div>
      `,
      
      // Component styles
      stylesheet: `
        .counter {
          font-family: sans-serif;
          text-align: center;
          margin: 2rem auto;
          padding: 1rem;
          border: 1px solid #ccc;
          border-radius: 4px;
          max-width: 300px;
        }
        
        button {
          padding: 0.5rem 1rem;
          margin: 0 0.5rem;
          font-size: 1rem;
          cursor: pointer;
        }
      `
    };
    
    // Render the counter component to the DOM
    lips.render('Counter', counterTemplate).appendTo('#app');
  </script>
</body>
</html>

This simple example demonstrates several of Lips’ core features:

Project Structure

For larger applications, you might want to organize your code into separate files. Here’s a recommended structure for a Lips project:

my-lips-app/
├── index.html
├── main.css
├── components/
│   ├── Header/
│       ├── header.js
│       └── header.css
│   ├── Footer/
│       ├── footer.js
│       └── footer.css
│   └── TodoList/
│       ├── todoList.js
│       └── todoList.css
└── app.js

Example app.js:

// Import Lips
import Lips from '@lipsjs/lips';

// Import components
import Header from './components/Header.js';
import Footer from './components/Footer.js';
import TodoList from './components/TodoList.js';

// Initialize Lips
const lips = new Lips({
  debug: true,
  context: {
    theme: 'light',
    user: null
  }
});

// Register components
lips.register('header', Header);
lips.register('footer', Footer);
lips.register('todo-list', TodoList);

// Root component
const App = {
  default: `
    <div class="app">
      <header/>
      <main>
        <todo-list/>
      </main>
      <footer/>
    </div>
  `,
  stylesheet: `
    .app {
      display: flex;
      flex-direction: column;
      min-height: 100vh;
    }
    
    main {
      flex: 1;
      padding: 1rem;
    }
  `
};

// Render the app
lips.root(App, '#app');

Example component file (TodoList.js):

export default {
  state: {
    todos: [
      { id: 1, text: 'Learn Lips', completed: false },
      { id: 2, text: 'Build an app', completed: false }
    ],
    newTodo: ''
  },
  
  handler: {
    addTodo() {
      if (this.state.newTodo.trim()) {
        this.state.todos.push({
          id: Date.now(),
          text: this.state.newTodo,
          completed: false
        });
        this.state.newTodo = '';
      }
    },
    
    updateNewTodo(e) {
      this.state.newTodo = e.target.value;
    },
    
    toggleTodo(id) {
      const todo = this.state.todos.find(t => t.id === id);
      if (todo) {
        todo.completed = !todo.completed;
      }
    }
  },
  
  default: `
    <div class="todo-list">
      <h2>Todo List</h2>
      
      <div class="add-todo">
        <input type="text" 
                placeholder="Add new todo" 
                value=state.newTodo 
                on-input(updateNewTodo)/>
        <button on-click(addTodo)>Add</button>
      </div>
      
      <ul>
        <for [todo] in=state.todos>
          <li class="todo-item {todo.completed ? 'completed' : ''}">
            <input type="checkbox" 
                    checked=todo.completed 
                    on-change(toggleTodo, todo.id)/>
            <span>{todo.text}</span>
          </li>
        </for>
      </ul>
    </div>
  `,
  
  stylesheet: `
    .todo-list {
      max-width: 500px;
      margin: 0 auto;
    }
    
    .add-todo {
      display: flex;
      margin-bottom: 1rem;
    }
    
    .add-todo input {
      flex: 1;
      padding: 0.5rem;
      margin-right: 0.5rem;
    }
    
    .todo-item {
      display: flex;
      align-items: center;
      padding: 0.5rem 0;
    }
    
    .todo-item.completed span {
      text-decoration: line-through;
      opacity: 0.6;
    }
    
    .todo-item input {
      margin-right: 0.5rem;
    }
  `
};

ES module approach of TodoList.js file:

This structured approachs helps keep your code organized and maintainable as your application grows.

export const state = {
  todos: [
    { id: 1, text: 'Learn Lips', completed: false },
    { id: 2, text: 'Build an app', completed: false }
  ],
  newTodo: ''
}

export const handler = {
  addTodo() {
    if (this.state.newTodo.trim()) {
      this.state.todos.push({
        id: Date.now(),
        text: this.state.newTodo,
        completed: false
      });
      this.state.newTodo = '';
    }
  },
  
  updateNewTodo(e) {
    this.state.newTodo = e.target.value;
  },
  
  toggleTodo(id) {
    const todo = this.state.todos.find(t => t.id === id);
    if (todo) {
      todo.completed = !todo.completed;
    }
  }
}
  
export default `
  <div class="todo-list">
    <h2>Todo List</h2>
    
    <div class="add-todo">
      <input type="text" 
              placeholder="Add new todo" 
              value=state.newTodo 
              on-input(updateNewTodo)/>
      <button on-click(addTodo)>Add</button>
    </div>
    
    <ul>
      <for [todo] in=state.todos>
        <li class="todo-item {todo.completed ? 'completed' : ''}">
          <input type="checkbox" 
                  checked=todo.completed 
                  on-change(toggleTodo, todo.id)/>
          <span>{todo.text}</span>
        </li>
      </for>
    </ul>
  </div>
`
  
export const stylesheet = `
  .todo-list {
    max-width: 500px;
    margin: 0 auto;
  }
  
  .add-todo {
    display: flex;
    margin-bottom: 1rem;
  }
  
  .add-todo input {
    flex: 1;
    padding: 0.5rem;
    margin-right: 0.5rem;
  }
  
  .todo-item {
    display: flex;
    align-items: center;
    padding: 0.5rem 0;
  }
  
  .todo-item.completed span {
    text-decoration: line-through;
    opacity: 0.6;
  }
  
  .todo-item input {
    margin-right: 0.5rem;
  }
`;

Lips works well with this modular approach, making it easy to compose complex UIs from simple, reusable components.