Step 2: Your First Component
In this step, you will create TodoItem and TodoList components, learn the single-file component structure, pass props between components, and apply scoped styles.
The Single-File Component Structure
Every .akash file has three optional sections:
<script>
// Imports, signals, logic — runs once when the component is created
</script>
<template>
<!-- HTML markup with reactive expressions -->
</template>
<style scoped>
/* CSS scoped to this component */
</style>The compiler transforms this into a defineComponent() call at build time. You never need to call defineComponent() manually when using .akash files.
Create the Todo Type
First, create a shared type for todo items. Create src/types.ts:
export interface Todo {
id: string;
text: string;
completed: boolean;
createdAt: number;
}Create the TodoItem Component
Create the file src/components/TodoItem.akash:
<script>
import type { Todo } from '../types';
// Props are declared as top-level `export let` bindings
export let todo: Todo;
export let onToggle: (id: string) => void;
export let onDelete: (id: string) => void;
</script>
<template>
<div class="todo-item" class:completed={todo.completed}>
<label class="todo-label">
<input
type="checkbox"
checked={todo.completed}
on:change={() => onToggle(todo.id)}
/>
<span class="todo-text">{todo.text}</span>
</label>
<button class="todo-delete" on:click={() => onDelete(todo.id)}>
Delete
</button>
</div>
</template>
<style scoped>
.todo-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border, #e2e8f0);
transition: background-color 0.15s;
}
.todo-item:hover {
background-color: var(--hover, #f7fafc);
}
.todo-label {
display: flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
flex: 1;
}
.todo-text {
font-size: 1rem;
}
.completed .todo-text {
text-decoration: line-through;
opacity: 0.5;
}
.todo-delete {
background: none;
border: 1px solid var(--danger, #e53e3e);
color: var(--danger, #e53e3e);
border-radius: 4px;
padding: 0.25rem 0.5rem;
cursor: pointer;
font-size: 0.8rem;
}
.todo-delete:hover {
background: var(--danger, #e53e3e);
color: white;
}
</style>Let's break down what is new here:
Props with export let
<script>
export let todo: Todo;
export let onToggle: (id: string) => void;
</script>In .akash files, export let declares a prop. The parent component passes values in, and the component reads them. This is similar to Svelte's prop syntax.
Conditional Classes with class:
<div class="todo-item" class:completed={todo.completed}>The class:name={condition} directive adds the class completed when todo.completed is truthy. This is a compile-time feature -- no runtime class merging needed.
Event Handlers with on:
<input on:change={() => onToggle(todo.id)} />The on:eventname directive attaches event listeners. The handler is a regular function.
Naming Conventions
AkashJS components use PascalCase filenames: TodoItem.akash, TodoList.akash. This matches the import name and makes components easy to spot in your file tree.
Create the TodoList Component
Now create src/components/TodoList.akash to render a list of items:
<script>
import type { Todo } from '../types';
import TodoItem from './TodoItem.akash';
export let todos: Todo[];
export let onToggle: (id: string) => void;
export let onDelete: (id: string) => void;
</script>
<template>
<div class="todo-list">
{#if todos.length === 0}
<div class="empty-state">
<p>No todos yet. Add one above!</p>
</div>
{:else}
{#each todos as todo (todo.id)}
<TodoItem
todo={todo}
onToggle={onToggle}
onDelete={onDelete}
/>
{/each}
{/if}
</div>
</template>
<style scoped>
.todo-list {
border: 1px solid var(--border, #e2e8f0);
border-radius: 8px;
overflow: hidden;
}
.empty-state {
padding: 2rem;
text-align: center;
color: var(--muted, #a0aec0);
}
</style>Template Control Flow
AkashJS uses block syntax for control flow inside templates:
Conditionals:
{#if condition}
<p>Shown when true</p>
{:else}
<p>Shown when false</p>
{/if}Loops:
{#each items as item (item.id)}
<ItemComponent item={item} />
{/each}The (item.id) after as item is the key expression. It tells AkashJS how to efficiently reconcile the list when items are added, removed, or reordered. Always provide a key for lists that change.
Component Composition
<script>
import TodoItem from './TodoItem.akash';
</script>Importing a .akash component and using it in the template is all you need. No registration step, no declarations array. Import it, use it.
Wire Everything Together
Update src/pages/Home.akash to use your new components:
<script>
import type { Todo } from '../types';
import TodoList from '../components/TodoList.akash';
// Hardcoded data for now — we'll make this reactive in the next step
const todos: Todo[] = [
{ id: '1', text: 'Learn AkashJS', completed: false, createdAt: Date.now() },
{ id: '2', text: 'Build a todo app', completed: false, createdAt: Date.now() },
{ id: '3', text: 'Ship to production', completed: false, createdAt: Date.now() },
];
function handleToggle(id: string) {
console.log('Toggle:', id);
}
function handleDelete(id: string) {
console.log('Delete:', id);
}
</script>
<template>
<div class="home">
<h2>All Todos</h2>
<TodoList
todos={todos}
onToggle={handleToggle}
onDelete={handleDelete}
/>
</div>
</template>
<style scoped>
.home {
display: flex;
flex-direction: column;
gap: 1rem;
}
</style>Save all files and check the browser. You should see your three todo items rendered with checkboxes and delete buttons.
Try It
Experiment with what you have built:
- Add a fourth todo to the hardcoded array and see it appear
- Add a new prop
createdAtdisplay toTodoItemshowing the date - Try removing
scopedfrom a<style>block and see how styles leak to other components - Open the browser DevTools and inspect the generated HTML -- notice the scoped attribute AkashJS adds to elements
How does scoped CSS work?
The AkashJS compiler adds a unique data attribute (like data-v-a1b2c3) to every element in the component's template, then rewrites the CSS selectors to include that attribute. This ensures styles only apply to elements within that component, even if class names collide.
Summary
You now know how to:
- Structure a
.akashsingle-file component with<script>,<template>, and<style scoped> - Declare props with
export let - Use
class:for conditional CSS classes andon:for event handlers - Compose components by importing and rendering child components
- Use
{#if},{:else}, and{#each}for control flow in templates
The todo list renders, but nothing is interactive yet. The toggle and delete handlers just log to the console. Let's fix that with reactivity.
What's Next: Reactivity -- add signals to make the todo list interactive.