Your First App
Build a simple counter app to learn the basics of Hypen
Your First App
Let's build a simple counter app to learn the basics of Hypen. By the end of this tutorial, you'll understand:
- How to create components
- How to style with applicators
- How to manage state with modules
- How to handle user interactions
What We're Building
A counter with increment and decrement buttons:
┌─────────────────────────┐
│ │
│ Count: 0 │
│ │
│ [ - ] [ + ] │
│ │
└─────────────────────────┘Step 1: Create the Project
Use the Hypen CLI to scaffold a new project:
# Install the CLI if you haven't already
bun install -g @hypen-space/cli
# Create a new project
hypen init counter-app
cd counter-appThe CLI creates a project structure with a starter component. Let's replace it with our counter.
Step 2: Create the Template
Create src/components/Counter/component.hypen:
module Counter {
Column {
Text("Count: ${state.count}")
.fontSize(48)
.fontWeight("bold")
.color("#1F2937")
Row {
Button {
Text("-")
.fontSize(24)
.color("#FFFFFF")
}
.onClick(@actions.decrement)
.backgroundColor("#EF4444")
.padding(horizontal: 24, vertical: 12)
.borderRadius(8)
Spacer()
.width(24)
Button {
Text("+")
.fontSize(24)
.color("#FFFFFF")
}
.onClick(@actions.increment)
.backgroundColor("#22C55E")
.padding(horizontal: 24, vertical: 12)
.borderRadius(8)
}
}
.padding(32)
.gap(24)
.horizontalAlignment("center")
}Let's break this down:
The Module Declaration
module Counter {
// ... UI goes here
}module tells Hypen this component has state. The name Counter must match the TypeScript module file.
State Bindings
Text("Count: ${state.count}")${state.count} is a state binding. It automatically updates when state.count changes.
Actions
Button { Text("+") }
.onClick(@actions.increment).onClick(@actions.increment) is an event applicator that triggers the increment action defined in the module.
Applicators
.fontSize(48)
.fontWeight("bold")
.backgroundColor("#EF4444")Applicators style the component. They're chained with dots and applied in order.
Step 3: Create the Module
Create src/components/Counter/component.ts:
import { app } from "@hypen-space/core";
// Define the state shape
interface CounterState {
count: number;
}
// Create the module
export default app
// Set initial state
.defineState<CounterState>({ count: 0 })
// Handle increment action - receives context object
.onAction("increment", async ({ state }) => {
state.count++;
// State changes sync automatically via Proxy
})
// Handle decrement action
.onAction("decrement", async ({ state }) => {
state.count--;
});Let's break this down:
Defining State
.defineState<CounterState>({ count: 0 })Sets the initial state. The generic <CounterState> provides TypeScript type checking.
Handling Actions
.onAction("increment", async ({ action, state, next, context }) => {
state.count++;
})action- Contains event name, payload, and senderstate- The current state (mutable, auto-synced via Proxy)next.router- HypenRouter for programmatic navigationcontext- GlobalContext for cross-module communication
Step 4: Update the Entry Point
The CLI created src/main.ts for you. Update it to render your Counter component:
import { render } from "@hypen-space/web";
async function main() {
await render("Counter", "#app");
}
main();That's it! The CLI already set up the HTML and build configuration.
Step 5: Run It!
Start the development server using the CLI:
hypen devOpen http://localhost:3000 in your browser and you should see the counter. Click the buttons to increment and decrement!
What's Happening
- Render -
render("Counter", "#app")loads the Counter component - Parse - The Hypen engine parses the
.hypentemplate - Initial Render - Creates DOM elements with
count: 0 - Click - User clicks "+" button
- Action -
@actions.incrementtriggers the action handler - State Update -
state.count++modifies the state - Auto-Sync - State changes are tracked via Proxy and sync automatically
- Re-render - Engine diffs and updates only the text "Count: 1"
Challenges
Try extending the counter:
1. Add a Reset Button
Add a button that sets count back to 0:
Button { Text("Reset") }
.onClick(@actions.reset).onAction("reset", async ({ state }) => {
state.count = 0;
})2. Add a Step Size
Let users increment by different amounts:
Row {
Button { Text("+1") }.onClick(@actions.increment, step: 1)
Button { Text("+5") }.onClick(@actions.increment, step: 5)
Button { Text("+10") }.onClick(@actions.increment, step: 10)
}.onAction("increment", async ({ action, state }) => {
const step = action.payload.step || 1;
state.count += step;
})3. Prevent Negative Numbers
.onAction("decrement", async ({ state }) => {
if (state.count > 0) {
state.count--;
}
})Next Steps
You've built your first Hypen app! Continue learning:
- Basics Guide - Learn the syntax in depth
- Components - All built-in components
- Styling - Master applicators
- State & Modules - Advanced state management