Hypen
Getting Started

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-app

The 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 sender
  • state - The current state (mutable, auto-synced via Proxy)
  • next.router - HypenRouter for programmatic navigation
  • context - 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 dev

Open http://localhost:3000 in your browser and you should see the counter. Click the buttons to increment and decrement!

What's Happening

  1. Render - render("Counter", "#app") loads the Counter component
  2. Parse - The Hypen engine parses the .hypen template
  3. Initial Render - Creates DOM elements with count: 0
  4. Click - User clicks "+" button
  5. Action - @actions.increment triggers the action handler
  6. State Update - state.count++ modifies the state
  7. Auto-Sync - State changes are tracked via Proxy and sync automatically
  8. 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: