HypenHypen
Guide

Basics

Fundamental concepts of the Hypen language

Basics

This guide covers the fundamental concepts of the Hypen language.

Components

Components are the building blocks of Hypen UIs. They're declared by name followed by optional arguments and children.

// Component with no arguments
Spacer()

// Component with arguments
Text("Hello World")

// Component with children
Column {
    Text("First")
    Text("Second")
}

// Component with arguments AND children
Button {
    Text("Submit")
}.onClick(@actions.submit)

Component Names

Component names are PascalCase (start with uppercase):

// Correct
Column { }
Text("Hi")
MyCustomComponent()

// Wrong - will be treated as unknown
column { }
text("Hi")

Arguments

Arguments pass data to components. They can be positional or named.

Positional Arguments

Text("Hello")           // Single positional
Image("photo.jpg")      // URL as positional

Named Arguments

Text(text: "Hello")
Input(placeholder: "Enter name")
    .value(${state.name})

Argument Types

// Strings (double or single quotes)
Text("Hello")
Text('Contains "double quotes" inside')
Text("Escaped \"quotes\" also work")

// Numbers
.padding(16)
.fontSize(24)
.opacity(0.5)

// Booleans
Button(disabled: true)
Input(multiline: false)

// Lists
Row(items: ["a", "b", "c"])

// Maps
Card(style: { elevation: 4, rounded: true })

// State bindings
Text("Count: ${state.count}")

// Actions (via applicators)
Button { Text("Save") }
    .onClick(@actions.save)

Applicators

Applicators modify a component's appearance or behavior. They're chained with dot notation.

Text("Styled text")
    .fontSize(18)
    .fontWeight("bold")
    .color("#3B82F6")
    .padding(16)

Chaining

Applicators are applied in order, from top to bottom:

Box { }
    .backgroundColor("blue")    // Applied first
    .padding(16)                // Applied second
    .borderRadius(8)            // Applied third

Common Applicators

// Spacing
.padding(16)
.margin(8)
.gap(12)

// Size
.width(200)
.height(100)
.fillMaxWidth(true)

// Colors
.color("#333333")
.backgroundColor("#F3F4F6")

// Typography
.fontSize(18)
.fontWeight("bold")
.textAlign("center")

// Border
.borderRadius(8)
.borderWidth(1)
.borderColor("#E5E7EB")

// Layout
.horizontalAlignment("center")
.verticalAlignment("center")

State Bindings

State bindings connect your UI to reactive data using ${state.path} syntax.

// Simple binding
Text("Hello, ${state.name}!")

// Nested paths
Text("${state.user.profile.displayName}")

// In string interpolation
Text("You have ${state.messages.length} messages")

When the referenced state changes, the UI automatically updates.

Actions

Actions trigger handlers in your module using @actions.actionName syntax via event applicators.

// Basic action
Button { Text("Save") }
    .onClick(@actions.save)

// Action with additional data
Button { Text("Delete") }
    .onClick(@actions.deleteItem, id: ${state.currentId})

// Multiple events
Input(placeholder: "Type here")
    .onInput(@actions.updateText)
    .onFocus(@actions.inputFocused)
    .onBlur(@actions.inputBlurred)

Children

Components can contain other components as children using braces { }.

Column {
    Text("First child")
    Text("Second child")
    Row {
        Text("Nested child")
    }
}

Nesting

There's no limit to nesting depth:

Column {
    Card {
        Row {
            Image("avatar.png")
            Column {
                Text("John Doe")
                Text("@johndoe")
            }
        }
    }
}

Control Flow

Hypen provides first-class control flow components for building dynamic UIs. These don't create DOM elements — their children render directly into the parent. For a complete guide, see Control Flow.

ForEach — Rendering Lists

Iterate over an array from state and render children for each item. Use @item to reference the current element, and provide key for efficient updates when the list changes.

Column {
    ForEach(items: @state.todos, key: "id") {
        Row {
            Checkbox {}
                .checked(@item.completed)
                .onChange(@actions.toggleTodo)

            Text(@item.title)
                .color("${item.completed ? '#9CA3AF' : '#111827'}")
        }
        .padding(8)
        .gap(8)
    }
}

Arguments:

  • items: Array binding from state (e.g., @state.todos)
  • key (optional but recommended): Property name for stable identity (e.g., "id")
  • as (optional): Custom variable name (default is "item")

When — Pattern Matching

Match a value against multiple patterns and render the first matching branch.

When(value: @state.status) {
    Case(match: "loading") {
        Center { Spinner() }
    }
    Case(match: "error") {
        Column {
            Text("Something went wrong")
            Button { Text("Retry") }
                .onClick(@actions.reload)
        }
    }
    Case(match: "success") {
        ContentView()
    }
    Else {
        Text("Unknown status")
    }
}

Match patterns support exact values ("loading"), multiple values ([200, 201, 204]), wildcards ("_"), and expressions ("${value >= 90}").

If — Boolean Conditionals

Show or hide UI sections based on a boolean condition.

If(condition: @state.isLoggedIn) {
    Text("Welcome back, ${state.user.name}!")
    Else {
        Button { Text("Sign In") }
            .onClick(@actions.signIn)
    }
}

For simple style changes, you don't need If — use expression bindings directly:

// No If needed — just change the color
Text(@state.status)
    .color("${state.status == 'error' ? '#EF4444' : '#10B981'}")

Comments

// Single line comment

/*
   Multi-line
   comment
*/

Column {
    Text("Visible")
    // Text("Hidden - commented out")
}

Modules

Modules are components with state. Declare them with the module keyword.

module Counter {
    Column {
        Text("Count: ${state.count}")
        Button { Text("+") }
            .onClick(@actions.increment)
    }
}

The module name must match the TypeScript file that defines its state and actions.

Complete Example

module UserProfile {
    Column {
        // Header
        Row {
            Image(${state.user.avatar})
                .size(64)
                .borderRadius(32)

            Column {
                Text(${state.user.name})
                    .fontSize(20)
                    .fontWeight("bold")

                Text("@${state.user.username}")
                    .color("#6B7280")
            }
            .gap(4)
        }
        .gap(16)
        .padding(16)

        Divider()

        // Stats
        Row {
            Column {
                Text("${state.user.posts}")
                    .fontWeight("bold")
                Text("Posts")
                    .fontSize(12)
            }

            Column {
                Text("${state.user.followers}")
                    .fontWeight("bold")
                Text("Followers")
                    .fontSize(12)
            }

            Column {
                Text("${state.user.following}")
                    .fontWeight("bold")
                Text("Following")
                    .fontSize(12)
            }
        }
        .horizontalAlignment("space-around")
        .padding(16)

        // Actions
        Button {
            Text(${state.isFollowing} ? "Unfollow" : "Follow")
        }
        .onClick(@actions.follow)
        .backgroundColor(${state.isFollowing} ? "#E5E7EB" : "#3B82F6")
        .padding(horizontal: 24, vertical: 12)
        .borderRadius(8)
        .fillMaxWidth(true)
        .margin(16)
    }
}

Next Steps