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 App 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", ({ state }) => {
state.count++;
// State changes sync automatically via Proxy
})
// Handle decrement action
.onAction("decrement", ({ state }) => {
state.count--;
})
.build();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, context }) => {
state.count++;
})action- Contains event name, payload, and senderstate- The current state (mutable, auto-synced via Proxy)context- GlobalContext for cross-module communicationcontext.router- HypenRouter for programmatic navigation
Step 4: 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!
Preview with Hypen Studio
For a richer development experience, launch Hypen Studio — a full in-browser IDE with a code editor, live preview, state inspector, action log, and time-travel debugging:
hypen studioStudio opens at http://localhost:5173. You can edit your component and module files directly in the editor and see changes reflected in the live preview instantly. Use the state inspector to watch state.count update as you click buttons, and the action log to verify each increment and decrement action fires correctly.
Preview on Android and iOS
Run your counter app on a real device or simulator:
# Android (requires adb)
hypen run android
# iOS Simulator (requires Xcode)
hypen run iosThe CLI downloads a lightweight runner app, detects your connected devices, and launches your counter with hot reload. The same Hypen code renders natively on each platform — DOM elements on Web, Jetpack Compose on Android, SwiftUI on iOS.
Combine device preview with Studio to edit in the browser and see changes live on the device:
hypen run android --studio
hypen run ios --studioWhat's Happening
- Discovery - The CLI discovers the Counter component in
src/components/ - 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<{ step: number }>("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