# Ziex
> Full-stack web framework for Zig. Compile-time safety, deterministic performance, absolute simplicity, and a delightful developer experience.
ZX files use familiar HTML-style markup with full access to Zig control flow (`if`/`else`, `for`/`while`, `switch`). File-system routing, API routes, WebSockets, component/page caching, KV storage, and SQLite are built in. Deploy as a standalone binary, WASI edge module, or static site.
Important notes:
- ZX is stricter than HTML: all tags must be closed (including self-closing tags like ` `)
- Components and pages receive an allocator (usually `ctx.arena` for request-scoped allocations)
- File-system routing maps `pages/` and `routes/` to URLs; dynamic segments use `[param]` folders
## Docs
- [Learn](https://ziex.dev/learn): Quick start covering core ZX concepts
- [Reference](https://ziex.dev/reference): Language and API reference
- [Examples](https://ziex.dev/examples): Interactive feature demos
- [Playground](https://ziex.dev/playground): Try ZX in the browser
- [GitHub](https://github.com/ziex-dev/ziex): Source repository
## Examples
### Control Flow
#### if
```zx
const ControlIfProps = struct { is_admin: bool };
pub fn ControlIf(
allocator: zx.Allocator,
props: ControlIfProps,
) zx.Component {
return (
{if (props.is_admin) (
Admin
) else (
Member
)}
);
}
```
#### if optional capture
```zx
pub fn ControlIfOptional(allocator: zx.Allocator) zx.Component {
const maybe_name: ?[]const u8 = "Zig";
return (
{if (maybe_name) |name| ({name} )}
);
}
```
#### if error capture
```zx
pub fn ControlIfError(allocator: zx.Allocator) zx.Component {
const parsed =
std.fmt.parseInt(u32, "42", 10);
return (
{if (parsed) |value| (
{value}
) else |err| (
{@errorName(err)}
)}
);
}
```
#### switch
```zx
const Role = enum { admin, member, guest };
const ControlSwitchProps = struct { role: Role };
pub fn ControlSwitch(allocator: zx.Allocator, props: ControlSwitchProps) zx.Component {
return (
{switch (props.role) {
.admin => (Admin ),
.member => (Member ),
else => (Guest ),
}}
);
}
```
#### for
```zx
const ControlForProps = struct { items: []const []const u8 };
pub fn ControlFor(allocator: zx.Allocator, props: ControlForProps) zx.Component {
return (
{for (props.items) |item| ({item} )}
);
}
```
#### while
```zx
pub fn ControlWhile(allocator: zx.Allocator) zx.Component {
var i: usize = 0;
return (
{while (i < 3) : (i += 1) (Item {i + 1} )}
);
}
```
#### while optional capture
```zx
pub fn ControlWhileOptional(allocator: zx.Allocator) zx.Component {
var maybe: ?u32 = 1;
return (
{while (maybe) |value| : (maybe = if (value < 3) value + 1 else null) (
{value}
)}
);
}
```
#### while error capture
```zx
pub fn ControlWhileError(allocator: zx.Allocator) zx.Component {
var n: u32 = 1;
return (
{while (parseOnce(&n)) |value| (
{value}
) else |err| (
{@errorName(err)}
)}
);
}
fn parseOnce(n: *u32) !u32 {
if (n.* < 100) {
n.* += 1;
return n.*;
}
return error.Done;
}
```
### Caching
#### component
```zx
pub fn CacheComponent(
allocator: zx.Allocator,
) !zx.Component {
return ( );
}
fn Slow(allocator: zx.Allocator) !zx.Component {
try std.Io
.sleep(zx.io(), .fromSeconds(2), .awake);
return (Slow
);
}
```
#### page
```zx
pub fn Caching__Page(
ctx: zx.PageContext,
) !zx.Component {
try std.Io
.sleep(zx.io(), .fromSeconds(2), .awake);
return (
Expensive Page
I take too long to load.
);
}
pub const CachingPage__options = zx.PageOptions{
.caching = .{ .ttl = .fromSeconds(300) },
};
```
### File System Routing
#### page
```zx
pub fn Routing__Page(
ctx: zx.PageContext,
) zx.Component {
return (
Home
);
}
```
#### layout
```zx
pub fn Layout(
ctx: zx.LayoutContext,
children: zx.Component,
) zx.Component {
return (
{children}
);
}
```
### Component
#### basics
```zx
const ButtonProps = struct { label: []const u8 };
pub fn Button(
ctx: *zx.ComponentCtx(ButtonProps),
) zx.Component {
return (
Click {ctx.props.label}
);
}
```
#### fragment
```zx
pub fn Fragment(
allocator: zx.Allocator,
) zx.Component {
return (
One
<>
Two
Three
>
);
}
```
#### props
```zx
pub fn SpreadProps(
allocator: zx.Allocator,
) zx.Component {
const attrs =
.{ .class = "btn", .id = "cta" };
const class = "primary";
return (
);
}
```
#### attr
```zx
pub fn DynamicAttr(
allocator: zx.Allocator,
) zx.Component {
const is_active = true;
const class = if (is_active) "active" else "idle";
const color = if (is_active) "green" else "red";
return (
);
}
```
### Dynamic Path
```zx
pub fn Page(ctx: zx.PageContext) zx.Component {
const id = ctx.request.params.get("id");
return (
);
}
```
### Expressions
```zx
pub fn Expressions(allocator: zx.Allocator) zx.Component {
const name = "Alice";
const count: u32 = 42;
const price: f32 = 19.99;
const active = true;
return (
{name}
{count}
${price}
{active}
);
}
```
### Builtin Attributes
#### allocator
```zx
pub fn AllocatorDemo(ctx: zx.PageContext) zx.Component {
const message = "Allocator is inherited by children";
return (
{message}
Use ctx.arena for request-scoped allocations.
);
}
```
#### escaping
```zx
pub fn RawHtml(allocator: zx.Allocator) zx.Component {
const trusted = "Trusted HTML ";
return (
{trusted}
{trusted}
);
}
```
### Importing
```zx
pub fn ImportDemo(allocator: zx.Allocator) zx.Component {
return (
);
}
```
### Component
#### children
```zx
const CardChildrenProps = struct { title: []const u8, children: zx.Component };
pub fn CardDemo(allocator: zx.Allocator) zx.Component {
return (
This content is passed as children.
);
}
fn Card(allocator: zx.Allocator, props: CardChildrenProps) zx.Component {
return (
{props.title}
{props.children}
);
}
```
### API Route
```zx
pub fn PUT(ctx: zx.RouteContext) !void {
try ctx.response.json(.{ .status = "ok" }, .{});
}
pub fn POST(ctx: zx.RouteContext) !void {
const Data = struct { id: u32 };
const data =
try ctx.request.json(Data, .{});
try ctx.response.json(.{
.id = if (data) |d| d.id else 0,
.created = true,
}, .{});
}
```
### WebSocket
```zx
pub fn GET(ctx: zx.RouteContext) !void {
try ctx.socket.upgrade({});
}
pub fn Socket(ctx: zx.SocketContext) !void {
try ctx.socket.write(
try ctx.fmt("{s}", .{ctx.message}),
);
}
pub fn SocketOpen(ctx: zx.SocketOpenContext) !void {
try ctx.socket.write("Opened!");
}
```
### Client-side Rendering
```zx
pub fn Client(allocator: zx.Allocator) zx.Component {
return ( );
}
pub fn Counter(ctx: *zx.ComponentCtx(void)) zx.Component {
const count = ctx.state(i32, 0);
return (
Click Me: {count}
);
}
fn increment(e: *zx.client.Event.Stateful) void {
const count = e.state(i32);
count.set(count.get() + 1);
}
```
### Plugin
#### tailwind
```zx
pub fn buildTailwind(b: *std.Build) !void {
const exe = b.addExecutable(.{ .name = "my-app" });
var zx_build = try ziex.init(b, exe, .{});
var assetsdir = zx_build.assetsdir;
zx_build.plugin(
zx.plugins.tailwind(b, .{
.input = b.path("app/assets/style.css"),
.output = assetsdir.path(b, "style.css"),
}),
);
}
```
#### esbuild
```zx
pub fn buildEsbuild(b: *std.Build) !void {
const exe = b.addExecutable(.{ .name = "my-app" });
var zx_build = try ziex.init(b, exe, .{});
var assetsdir = zx_build.assetsdir;
zx_build.plugin(
zx.plugins.esbuild(b, .{
.input = b.path("app/main.ts"),
.output = assetsdir.path(b, "main.js"),
}),
);
}
```
### Learn
#### greeting
```zx
pub fn Learn__HelloWorld(allocator: zx.Allocator) zx.Component {
return (
Welcome to my app
);
}
fn Learn__Greeting(allocator: zx.Allocator) zx.Component {
return (Hello, World!
);
}
```
#### markup
```zx
pub fn Learn__AboutSection(allocator: zx.Allocator) zx.Component {
return (
About
Hello there. How are you?
);
}
fn Learn__AboutCard(allocator: zx.Allocator) zx.Component {
return (
User Profile
Welcome to the card component!
);
}
```
#### text expression
```zx
pub fn Learn__UserGreeting(allocator: zx.Allocator) zx.Component {
const user_name = "Alice";
const greeting = "Welcome back";
return (
{greeting}, {user_name}!
Your profile is ready.
);
}
```
#### format expression
```zx
pub fn Learn__ProductInfo(allocator: zx.Allocator) zx.Component {
const price: f32 = 19.99;
const quantity: u32 = 3;
const is_available = true;
return (
Price: ${price}
Quantity: {quantity}
Available: {is_available}
);
}
```
#### conditional
```zx
pub fn Learn__UserStatus(allocator: zx.Allocator) zx.Component {
const is_logged_in = true;
const is_admin = false;
return (
{if (is_logged_in) (Welcome back!
) else (Please log in.
)}
{if (is_admin) (Admin Panel )}
);
}
```
#### switch
```zx
const Learn__Role = enum { admin, member, guest };
pub fn Learn__RoleBadge(allocator: zx.Allocator) zx.Component {
const role: Learn__Role = .admin;
return (
{switch (role) {
.admin => (Admin ),
.member => ("Member"),
.guest => ("Guest"),
}}
);
}
```
#### list
```zx
pub fn Learn__ProductList(allocator: zx.Allocator) zx.Component {
const products = [_][]const u8{ "Apple", "Banana", "Orange" };
return (
Products
{for (products) |product| ({product} )}
);
}
```
#### props
```zx
const Learn__ButtonProps = struct {
title: []const u8 = "Click Me",
class: []const u8 = "btn",
};
pub fn Learn__ButtonDemo(allocator: zx.Allocator) zx.Component {
return (
);
}
fn Learn__Button(allocator: zx.Allocator, props: Learn__ButtonProps) zx.Component {
return ({props.title} );
}
```
#### page
```zx
pub fn Learn__Page(ctx: zx.PageContext) zx.Component {
return (
About Us
Welcome to our website!
Path: {ctx.request.pathname}
);
}
```
#### layout
```zx
pub fn Learn__Layout(ctx: zx.LayoutContext, children: zx.Component) zx.Component {
return (
My App
Home About
{children}
);
}
```
#### build manual
```zx
pub fn Learn__build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const app_exe = b.addExecutable(.{
.name = "ziex_app",
.root_module = b.createModule(.{
.root_source_file = b.path("app/main.zig"),
.target = target,
.optimize = optimize,
}),
});
_ = try ziex.init(b, app_exe, .{});
}
```
#### dynamic route
```zx
pub fn Learn__UserProfile(ctx: zx.PageContext) zx.Component {
const user_id = ctx.request.params.get("id") orelse "unknown";
return (
User Profile
User ID: {user_id}
);
}
```
#### fragment
```zx
pub fn Learn__FragmentDemo(allocator: zx.Allocator) zx.Component {
return (
<>
Welcome
Multiple elements without a wrapper
>
);
}
```
#### children
```zx
const Learn__CardProps = struct { title: []const u8, children: zx.Component };
pub fn Learn__CardDemo(allocator: zx.Allocator) zx.Component {
return (
This content is passed as children.
Click me
);
}
fn Learn__Card(allocator: zx.Allocator, props: Learn__CardProps) zx.Component {
return (
{props.title}
{props.children}
);
}
```
#### dynamic attributes
```zx
pub fn Learn__DynamicAttrs(allocator: zx.Allocator) zx.Component {
const class_name = "primary-btn";
const user_id = "user-123";
const is_active = true;
return (
Submit
Dynamic class
);
}
```
#### onclick
```zx
pub fn Learn__Interactivity(allocator: zx.Allocator) zx.Component {
return ( );
}
pub fn Learn__EventHandling(allocator: zx.Allocator) zx.Component {
return (
Click me
);
}
fn Learn__handleClick(_: zx.client.Event) void {
zx.log.info("Clicked!", .{});
}
fn Learn__handleInput(event: zx.client.Event) void {
const v = event.value() orelse "";
zx.log.info("Input: {s}", .{v});
}
```
### Reference Inline
#### attrs
```zx
pub fn unwrap__template_attrs() void {
const count = 42;
const name = "John";
// ...
_ = (
);
// Renders:
//
}
```
#### expr string
```zx
pub fn unwrap__ref_expr_string() void {
const name = "Alice";
// ...
_ = ({name}
); // Alice
}
```
#### expr int
```zx
pub fn unwrap__ref_expr_int() void {
const count: u32 = 42;
// ...
_ = ({count}
); // 42
}
```
#### expr float
```zx
pub fn unwrap__ref_expr_float() void {
const price: f32 = 19.99;
// ...
_ = (${price}
); // $19.99
}
```
#### expr bool
```zx
pub fn unwrap__ref_expr_bool() void {
const active = true;
// ...
_ = ({active}
); // true
}
```
#### expr enum
```zx
pub fn unwrap__ref_expr_enum() void {
const Status = enum { pending, approved, rejected };
const status = Status.approved;
// ...
_ = ({status}
); // approved
}
```
#### expr optional
```zx
pub fn unwrap__ref_expr_optional() void {
const maybe_name: ?[]const u8 = "Bob";
// ...
_ = ({maybe_name}
); // Bob
const no_name: ?[]const u8 = null;
// ...
_ = ({no_name}
); //
(empty)
}
```
#### expr component
```zx
pub fn unwrap__ref_expr_component() void {
const header = zx.Component{ .text = "Header" };
// ...
_ = ({header}
); // Renders the Header component
}
```
#### expr component array
```zx
pub fn unwrap__ref_expr_component_array() void {
const items: []const zx.Component = &.{ zx.Component{ .text = "A" }, zx.Component{ .text = "B" } };
// ...
_ = (); // Renders both Item components
}
```
#### optional capture
```zx
pub fn unwrap__ref_optional_capture() void {
const user_name: ?[]const u8 = "Alice";
// ...
_ = if (user_name) |name| (Welcome, {name}!
);
// With else branch:
_ = if (user_name) |name| (Hello, {name}
) else (Guest
);
// While with capture (iterator pattern):
var maybe_item: ?[]const u8 = "item";
_ = while (maybe_item) |item| : (maybe_item = null) ({item} );
}
```
#### error capture
```zx
pub fn unwrap__ref_error_capture() void {
const parsed = std.fmt.parseInt(u32, "42", 10);
// If with error capture:
_ = if (parsed) |value| (Value: {value}
) else |err| (Error: {@errorName(err)}
);
// While with error capture:
var first = true;
_ = while (parseOnceRefInline(&first)) |value| ({value} ) else |err| (Iteration ended: {@errorName(err)}
);
}
fn parseOnceRefInline(first: *bool) !u32 {
if (first.*) {
first.* = false;
return 1;
}
return error.Done;
}
```
#### spread attrs
```zx
pub fn unwrap__ref_spread_attrs() void {
const form_attrs = .{
.class = "form-control",
.@"data-validate" = "true",
};
const input_props = .{ .name = "email", .value = "test@example.com" };
// ...
_ = (
);
}
```
#### shorthand attrs
```zx
pub fn unwrap__ref_shorthand_attrs() void {
const class = "btn-primary";
const disabled = true;
const @"data-id" = "123";
_ = (Click );
// Renders: Click
}
```
#### template attrs
```zx
pub fn unwrap__ref_template_attrs() void {
const count = 42;
const name = "John";
// ...
_ = (
);
// Renders:
//
}
```
#### component context
```zx
pub fn RefInlineNoProps__Container(ctx: *zx.ComponentContext) zx.Component {
return (
{ctx.children}
);
}
```
#### component ctx props
```zx
pub const RefInline__CardProps = struct { title: []const u8 };
pub fn RefInline__Card(ctx: *zx.ComponentCtx(RefInline__CardProps)) zx.Component {
return (
{ctx.props.title}
{ctx.children}
);
}
```
#### multiline strings
```zx
pub fn unwrap__ref_multiline_strings() void {
// Multiline strings use Zig's \\ syntax:
_ = (
{
\\const x = 1;
\\const y = 2;
\\const sum = x + y;
}
);
}
```
#### comments
```zx
pub fn unwrap__ref_comments() void {
// Single-line comments are supported in ZX:
_ = (
// This is a comment
Visible content
// Another comment
);
}
```
#### route ctx
```zx
pub fn RefInline__GET(ctx: zx.RouteContext) !void {
_ = ctx.request; // Request details
_ = ctx.response; // Set headers, body, json, etc.
_ = ctx.socket; // WebSocket upgrade and pub/sub
_ = ctx.arena; // Request-scoped allocator
}
```
#### socket ctx
```zx
pub fn RefInline__Socket(ctx: zx.SocketContext) !void {
_ = ctx.socket; // WebSocket interface
_ = ctx.message; // Received message data
_ = ctx.data; // Custom data from upgrade
_ = ctx.arena; // Message-scoped allocator
}
```
#### component caching basic
```zx
pub fn unwrap__ref_component_caching_basic() void {
// Cache for 10 seconds
_ = ();
// Cache for 5 minutes
_ = ();
// Cache for 1 hour
_ = ();
// Cache for 1 day
_ = ();
}
```
#### component caching key
```zx
pub fn unwrap__ref_component_caching_key() void {
// Cache with a custom key for manual invalidation
_ = ();
// Cache navigation with key
_ = ( );
}
```
#### page caching
```zx
pub fn RefPageCaching__Page(ctx: zx.PageContext) zx.Component {
return (
Cached Page
This page is cached for 5 minutes.
);
}
// Enable page-level caching
pub const RefPageCaching__options = zx.PageOptions{
.caching = .{ .ttl = .fromSeconds(300) }, // 5 minutes
};
```
#### layout caching
```zx
pub fn Ref__Layout(ctx: zx.LayoutContext, children: zx.Component) zx.Component {
return (
{children}
);
}
// Cache the entire layout for 1 hour
pub const RefLayout__options = zx.LayoutOptions{
.caching = .{ .ttl = .fromSeconds(3600) },
};
```
#### cache invalidation
```zx
pub fn unwrap__ref_cache_invalidation() void {
// Delete a specific cache entry by key
_ = zx.cache.del("cmp:user-profile");
// Delete all entries matching a prefix
// Useful for invalidating related content
_ = zx.cache.delPrefix("cmp:user") catch 0;
}
```
### Learn
#### state management
```zx
pub fn Learn__StateDemo(allocator: zx.Allocator) zx.Component {
return ( );
}
pub fn Learn__StatePanel(ctx: *zx.ComponentCtx(void)) zx.Component {
const count = ctx.state(i32, 0);
const title = ctx.state([]const u8, "Hello");
return (
App State: {title.get()}
Count: {count.get()}
Increment Count
);
}
fn Learn__incrementCount(value: i32) i32 {
return value + 1;
}
```
#### api route
```zx
pub fn Learn__GET(ctx: zx.RouteContext) !void {
try ctx.response.json(.{ .message = "Hello World!" }, .{});
}
```
#### enable kv
```zx
pub fn Learn__enableKvInit(b: *std.Build, app_exe: *std.Build.Step.Compile) !void {
_ = try ziex.init(b, app_exe, .{
.app = .{
.features = .{
.kv = .enabled,
},
},
});
}
```
#### enable db
```zx
pub fn Learn__enableDbInit(b: *std.Build, app_exe: *std.Build.Step.Compile) !void {
_ = try ziex.init(b, app_exe, .{
.app = .{
.features = .{
.sqlite = .enabled,
},
},
});
}
```
#### enable cache
```zx
pub fn Learn__enableCacheInit(b: *std.Build, app_exe: *std.Build.Step.Compile) !void {
_ = try ziex.init(b, app_exe, .{
.app = .{
.features = .{
.cache = .enabled,
},
},
});
}
```
#### kv basic
```zx
pub fn Learn__KvBasic(ctx: zx.PageContext) !zx.Component {
try zx.kv.put("greeting", "hello from kv", .{});
const value = try zx.kv.get(ctx.arena, "greeting");
try zx.kv.delete("greeting");
return (
Stored value: {value orelse "not found"}
);
}
```
#### kv typed
```zx
pub fn Learn__KvTyped(ctx: zx.PageContext) !zx.Component {
const User = struct {
id: u32,
name: []const u8,
active: bool,
};
try zx.kv.putAs("user:123", User{
.id = 123,
.name = "alice",
.active = true,
}, .{});
const user = try zx.kv.as(ctx.arena, "user:123", User);
return (
{if (user) |u| (
User: {u.name} (#{u.id})
) else (
Not found
)}
);
}
```
#### kv scoped
```zx
pub fn Learn__KvScoped(ctx: zx.PageContext) !zx.Component {
const sessions = zx.kv.scoped(.sessions);
try sessions.put("abc123", "token-xyz", .{});
const token = try sessions.get(ctx.arena, "abc123");
return (
Session token: {token orelse "none"}
);
}
```
#### db basic
```zx
pub fn Learn__DbBasic(ctx: zx.PageContext) !zx.Component {
_ = try zx.db.run(
\\CREATE TABLE IF NOT EXISTS posts (
\\ id INTEGER PRIMARY KEY,
\\ title TEXT NOT NULL
\\)
, .empty);
_ = try zx.db.run("INSERT INTO posts (title) VALUES (?1)", .{"Hello World"});
const row = try zx.db.get(ctx.arena, "SELECT title FROM posts ORDER BY id DESC LIMIT 1", .{});
return (
Latest post: {if (row) |r| r.text("title") else "none"}
);
}
```
#### db typed
```zx
pub fn Learn__DbTyped(ctx: zx.PageContext) !zx.Component {
const Post = struct {
id: i64,
title: []const u8,
};
_ = try zx.db.run(
\\CREATE TABLE IF NOT EXISTS blog_posts (
\\ id INTEGER PRIMARY KEY,
\\ title TEXT NOT NULL
\\)
, .empty);
_ = try zx.db.run("INSERT INTO blog_posts (title) VALUES (?1)", .{"ZX Guide"});
const posts = try zx.db.rows(ctx.arena, Post, "SELECT id, title FROM blog_posts", .{});
return (
{for (posts) |post| (
{post.title} (#{post.id})
)}
);
}
```
#### db transaction
```zx
pub fn Learn__DbTransaction(ctx: zx.PageContext) !zx.Component {
const Account = struct { id: i64, balance: i64 };
_ = try zx.db.run(
\\CREATE TABLE IF NOT EXISTS accounts (
\\ id INTEGER PRIMARY KEY,
\\ balance INTEGER NOT NULL
\\)
, .empty);
_ = try zx.db.run("INSERT OR REPLACE INTO accounts (id, balance) VALUES (1, 100), (2, 100)", .empty);
try zx.db.transaction({}, transfer);
const accounts = try zx.db.rows(ctx.arena, Account, "SELECT id, balance FROM accounts ORDER BY id", .{});
return (
{for (accounts) |account| (
Account #{account.id}: {account.balance}
)}
);
}
fn transfer(_: void, db: zx.Db) !void {
_ = try db.run("UPDATE accounts SET balance = balance - 10 WHERE id = ?1", .{1});
_ = try db.run("UPDATE accounts SET balance = balance + 10 WHERE id = ?1", .{2});
}
```
#### template attrs
```zx
pub fn unwrap__learn_template_attrs() void {
const id = 42;
// ...
_ = (Profile );
}
```
#### spread attrs
```zx
pub fn unwrap__learn_spread_attrs() void {
const attrs = .{ .class = "btn", .disabled = true };
// ...
_ = (Click );
}
```
#### client action
```zx
pub fn ContactForm(ctx: *zx.ComponentCtx(void)) zx.Component {
return (
);
}
fn ContactForm__onSubmit(a: *zx.client.Action.Stateful) void {
const data = a.data(struct { message: []const u8 });
zx.log.info("message: {s}", .{data.message});
}
```
#### server action
```zx
pub fn LoginForm(allocator: zx.Allocator) zx.Component {
return (
);
}
fn onLogin(a: zx.server.Action) void {
const data = a.data(struct { username: []const u8 });
zx.log.info("username: {s}", .{data.username});
}
```
### Reference Inline
#### csr simple handler
```zx
fn handleInput(event: zx.client.Event) void {
const value = event.value() orelse "";
zx.log.info("Input: {s}", .{value});
}
// Attach: oninput={handleInput}
```
#### csr stateful handler
```zx
pub fn CsrStateful__Counter(ctx: *zx.ComponentCtx(void)) zx.Component {
const count = ctx.state(i32, 0);
return (
{count}
);
}
fn CsrStateful__increment(e: *zx.client.Event.Stateful) void {
const count = e.state(i32); // same order as ctx.state()
count.set(count.get() + 1);
}
```
#### csr state bind
```zx
pub fn CsrStateBind__Counter(ctx: *zx.ComponentCtx(void)) zx.Component {
const count = ctx.state(i32, 0);
return (
{count}
);
}
fn CsrStateBind__increment(value: i32) i32 {
return value + 1;
}
```
## Optional
- [Framework comparisons](https://ziex.dev/vs): Ziex vs other web frameworks
- [Sitemap](https://ziex.dev/sitemap.xml): All indexable pages