Recipes

Examples & recipes.

Inline, copy-paste Aktion snippets that render live in this page. Flip between Preview and Source on any card, then swap themes with the picker to test palette contrast.

Want a focused tour of action Name() { ... } blocks? See the Actions guide — it walks through assignment, http({...}) mutations, emit, programmatic navigation via _route_.navigate("/path"), and inline js{ ... } side effects. For browser API access inside an action or effect body, see JavaScript interactions.

Inline examples

Chat-style assistant response

intro = Card([
  CardHeader("Onboarding plan", subtitle: "3 steps to set up your workspace")
])
steps = Steps([
  { title: "Invite teammates",        details: "Open the Members tab and send invites." },
  { title: "Connect a data source",   details: "Settings → Integrations → choose a provider." },
  { title: "Pick a starter template", details: "Seed the workspace with sample content." }
])
follow = FollowUpBlock([
  FollowUpItem("Show invite link"),
  FollowUpItem("List supported integrations"),
  FollowUpItem("Open the templates gallery")
])
_app_ = Stack([intro, steps, follow])

Form with state and a submit action

$title = ""
$priority = "medium"
$lastSaved = "Nothing yet"

action save() {
  $lastSaved = `${$title} (${$priority})`
  $title = ""
  $priority = "medium"
}

saveBtn = Button("Save", action: save, variant: "primary")
form = Form("ticket", saveBtn, [
  FormControl("Title", Input("title",
    placeholder: "Short summary",
    validation: ["required", "minLength:3"],
    value: $title)),
  FormControl("Priority", Select("priority", [
    SelectItem("low",    label: "Low"),
    SelectItem("medium", label: "Medium"),
    SelectItem("high",   label: "High")
  ], value: $priority))
])
status = Card([
  CardHeader("Last saved"),
  Markdown(`\`${$lastSaved}\``)
])
_app_ = Stack([form, status])

Action body — every step in one card

# Every action body composes assignment + emit + js{ ... } statements.
$count = 0
$log = "Click any button below."

action inc() {
  $count = $count + 1
  $log = `Incremented to ${$count}.`
}
action reset() {
  $count = 0
  $log = "Reset count to 0."
}
action chain() {
  $count = $count + 5
  $log = `Chained two writes — $count is now ${$count}.`
  emit "assistant-message" { message: "Bumped count by 5" }
}
action ask() {
  emit "assistant-message" { message: "Tell me more about action blocks." }
  $log = "Emitted an assistant-message event."
}
action openUrl() {
  js{ window.open("https://asfand-dev.github.io/aktion/actions.html",
                  "_blank", "noopener,noreferrer") }
  $log = "Opened a sanitised URL in a new tab."
}

counter = Card([
  CardHeader(`$count = ${$count}`, subtitle: "Read by every button below"),
  Buttons([
    Button("Increment",   action: inc,   variant: "primary"),
    Button("Reset",       action: reset, variant: "secondary"),
    Button("Chain steps", action: chain, variant: "primary")
  ])
])
extras = Card([
  CardHeader("More steps"),
  Buttons([
    Button("Emit assistant-message", action: ask),
    Button("Open URL",               action: openUrl, variant: "ghost"),
    Button("Implicit assistant-message")
  ])
])
status = Callout("info", title: "Status", text: $log, icon: "circle-info")

_app_ = Stack([counter, extras, status], direction: "column", gap: "l")

Tabs with shared state

$days = "7"

filter = FormControl("Range", Select("days", [
  SelectItem("7",  label: "7d"),
  SelectItem("14", label: "14d"),
  SelectItem("30", label: "30d")
], value: $days))

overview = TabItem("overview", label: "Overview", children: [
  Markdown(`Showing data for the last **${$days} days**.`),
  LineChart(["d1","d2","d3","d4","d5"],
    series: [Series("Events", values: [400,500,460,520,580])])
])
errors = TabItem("errors", label: "Errors", children: [
  Callout("success", title: "System healthy",
    text: "No incidents in the selected range.")
])
tabs = Tabs([overview, errors])
_app_ = Card([CardHeader("Operations"), filter, tabs])

Tags, separators, and a code block

header = CardHeader("Release notes", subtitle: "v1.2 highlights")
tags = BadgeList(["new", "tabs", "callout", "separator"], tone: "primary", size: "sm")
sep = Separator("horizontal")
snippet = CodeBlock("ts", code: "import { defineElement } from \"aktion\";\ndefineElement();")
group = CheckBoxGroup("notifications", [
  CheckBoxItem("Product updates", value: "product",    description: "Major releases & changelogs", checked: true),
  CheckBoxItem("Security alerts", value: "security",   description: "Critical fixes",                 checked: true),
  CheckBoxItem("Newsletter",      value: "newsletter", description: "Monthly digest")
])
_app_ = Stack([header, tags, sep, snippet, group])

Rich layouts (production-quality)

These compositions show what the LLM should reach for by default: a full app shell, a detail page, and a pricing page — all built from the same component vocabulary as the smaller examples above. Use them as a baseline for any "build me an app" request.

App shell with sidebar nav

action openSettings()    { emit "assistant-message" { message: "Open settings" } }
action startNewProject() { emit "assistant-message" { message: "Start a new project" } }

nav = Sidebar([
  SidebarSection("Workspace", items: [
    SidebarItem("Overview", icon: "house",    active: true),
    SidebarItem("Projects", icon: "folder",   badge: "12"),
    SidebarItem("Calendar", icon: "calendar"),
    SidebarItem("Messages", icon: "comments", badge: "3")
  ]),
  SidebarSection("Insights", items: [
    SidebarItem("Analytics", icon: "chart-pie"),
    SidebarItem("Reports",   icon: "chart-line"),
    SidebarItem("Billing",   icon: "credit-card")
  ])
], brand: "Acme HQ", footer: Stack([
  Avatar("Asha Patel", size: "sm"),
  Text("Settings", variant: "body-heavy")
], direction: "row", gap: "s"))

topbar = [
  StatusDot("Realtime", tone: "success", pulse: true),
  Buttons([
    Button("Invite",  variant: "ghost",   size: "small"),
    Button("Upgrade", variant: "primary", size: "small")
  ])
]

header = PageHeader("Overview",
  subtitle: "Everything happening across your workspace",
  actions: [Button("New project", action: startNewProject, variant: "primary")],
  badge: Badge("Live", tone: "success"))

kpis = Stats([
  StatCard("MRR",          value: "$48.2k", trend: "up",   delta: "+12%", icon: "sack-dollar"),
  StatCard("Active users", value: "2,184",  trend: "up",   delta: "+184", icon: "users"),
  StatCard("Open tickets", value: "23",     trend: "down", delta: "-9",   icon: "ticket"),
  StatCard("NPS",          value: "62",     trend: "flat", delta: "+1",   icon: "star")
])

projects = Card([
  SectionHeader("Active projects",
    eyebrow: "WORK",
    actions: [Button("View all", variant: "ghost", size: "small")]),
  List([
    ListItem("Streaming UI v2.4", meta: "Ada Lovelace · 3 open issues", icon: "rocket"),
    ListItem("Auth SDK rewrite",  meta: "Linus T · 1 open issue",       icon: "shield-halved"),
    ListItem("Onboarding revamp", meta: "Grace Hopper · awaiting QA",   icon: "bullseye")
  ])
])

content = Stack([header, kpis, projects], direction: "column", gap: "l")
_app_ = AppShell(sidebar: nav, content: content, topbar: topbar)

Detail page (PageHeader + DescriptionList + activity)

action editCustomer() { emit "assistant-message" { message: "Edit Acme Co customer" } }

header = PageHeader("Acme Co",
  subtitle: "Customer · since 2021 · Team plan",
  breadcrumbs: ["Workspace", "Customers", "Acme Co"],
  actions: [Button("Edit", action: editCustomer, variant: "primary")],
  badge: Badge("Active", tone: "success"))

info = Card([
  SectionHeader("Account details"),
  DescriptionList([
    DescriptionItem("Plan",      value: "Team"),
    DescriptionItem("MRR",       value: "$4,200", icon: "sack-dollar"),
    DescriptionItem("Owner",     value: "Asha Patel", icon: "user"),
    DescriptionItem("Renews",    value: "Aug 14"),
    DescriptionItem("Seats",     value: "18 / 25", icon: "users"),
    DescriptionItem("Region",    value: "EU")
  ], columns: 3)
])

activity = Card([
  SectionHeader("Recent activity"),
  ActivityLog([
    { actor: "Asha",  verb: "renewed", target: "Team plan", time: "2h ago", icon: "user" },
    { actor: "Linus", verb: "added",   target: "3 seats",   time: "yesterday", icon: "user" },
    { actor: "Grace", verb: "shared",  target: "invoice #1242", time: "Mon", icon: "user" }
  ])
])

_app_ = Stack([header, info, activity], direction: "column", gap: "l")

Pricing page (Hero + PricingCard grid + FAQ)

action upgrade(plan) { emit "assistant-message" { message: `Start a ${plan} trial` } }

hero = Hero("Simple, predictable pricing",
  subtitle: "Three plans. Cancel anytime. All include 14-day trials.",
  eyebrow: "Pricing")

plans = Grid([
  PricingCard("Starter",
    price: "$0",      period: "/mo",
    description: "Everything to get started",
    features: ["3 projects", "Community support", "Basic analytics"],
    cta: Button("Get started", () => upgrade("Starter"), variant: "secondary")),
  PricingCard("Pro",
    price: "$29",     period: "/mo",
    description: "For growing teams",
    features: ["Unlimited projects", "Priority support", "Custom themes", "API access"],
    cta: Button("Start trial", () => upgrade("Pro"), variant: "primary"),
    badge: "Most popular",
    highlighted: true),
  PricingCard("Team",
    price: "$99",     period: "/mo",
    description: "Collaboration at scale",
    features: ["Everything in Pro", "SSO", "Audit log", "Dedicated success manager"],
    cta: Button("Contact sales", () => upgrade("Team"), variant: "secondary"))
], columns: 3, gap: "l")

faqs = Card([
  SectionHeader("Frequently asked"),
  Accordion([
    AccordionItem("Can I switch plans later?",
      children: Text("Yes — upgrades are prorated and downgrades take effect at renewal.")),
    AccordionItem("Do you offer non-profit pricing?",
      children: Text("We offer 50% off Pro and Team plans for registered non-profits.")),
    AccordionItem("Is my data backed up?",
      children: Text("Daily encrypted backups with point-in-time restore on Team plans.")),
  ], showArrow: true)
])

_app_ = Stack([hero, plans, faqs], direction: "column", gap: "xl")

Richer composition primitives

These short recipes show off individual patterns: hero covers, media grids, trust strips, quick-action tiles, notification panels and chat threads.

Hero cover with caption + actions

action requestDemo() { emit "assistant-message" { message: "Book a 15-minute demo" } }

_app_ = Hero("Build assistant UIs in seconds",
  subtitle: "A declarative library for streaming, theme-aware UIs that ship with every LLM response.",
  eyebrow: "Aktion 0.5",
  primary:   Button("Book a demo", action: requestDemo, variant: "primary"),
  secondary: Button("Read the docs", variant: "ghost"),
  highlights: ["No bundler required", "Capability sandbox", "Seven built-in themes"])

MediaCard grid + Rating

cards = [
  MediaCard("Northern lights tour",
    src: "https://images.unsplash.com/photo-1531366936337-7c912a4589a7?auto=format&w=560&q=70",
    description: "Chase aurora trails across Tromsø with a small group.",
    tags: ["Outdoor", "Winter"],
    meta: "From $1,290",
    badge: "Bestseller"),
  MediaCard("Hidden cenote dive",
    src: "https://images.unsplash.com/photo-1505228395891-9a51e7e86bf6?auto=format&w=560&q=70",
    description: "Snorkel through underground caves of the Yucatán.",
    tags: ["Outdoor", "Adventure"],
    meta: "From $640"),
  MediaCard("Kyoto temples walk",
    src: "https://images.unsplash.com/photo-1545569341-9eb8b30979d9?auto=format&w=560&q=70",
    description: "Guided cultural walk through Higashiyama at dawn.",
    tags: ["Culture"],
    meta: "From $320")
]
ratings = Stack([
  Rating(4.7, count: 248, label: "Northern lights"),
  Rating(4.5, count: 132, label: "Cenote dive"),
  Rating(4.9, count: 412, label: "Kyoto temples")
], direction: "column", gap: "s")
_app_ = Stack([
  Grid(cards, columns: 3, gap: "l"),
  ratings
], direction: "column", gap: "l")

Stats trust strip

_app_ = Stats([
  StatCard("Teams", value: "1,240+", icon: "people-group"),
  StatCard("Daily messages", value: @Format(184000, "number"), icon: "comments"),
  StatCard("Uptime",   value: "99.99%",                    icon: "shield-halved"),
  StatCard("Regions",  value: "12",                        icon: "globe")
], align: "center")

Tile grid quick-actions

_app_ = Grid([
  Tile("Create project", icon: "folder-plus",
    description: "Spin up a fresh workspace.",
    tone: "primary"),
  Tile("Invite teammates", icon: "user-plus",
    description: "Share a link or send an email.",
    tone: "success"),
  Tile("Connect data",   icon: "plug",
    description: "Wire up a 3rd-party source."),
  Tile("Schedule a sync", icon: "calendar-days",
    description: "Book time with your CSM.")
], columns: 2, gap: "m")

Notification panel

_app_ = Card([
  SectionHeader("Notifications", subtitle: "Last 24 hours"),
  Stack([
    Notification("Deployment finished",
      description: "main → production · 4 m 12 s",
      time: "2m ago",
      icon: "rocket",
      tone: "success",
      unread: true),
    Notification("New trial signup",
      description: "Acme Co is on the Pro trial.",
      time: "12m ago",
      icon: "user-plus",
      tone: "primary"),
    Notification("Storage 80% full",
      description: "Consider archiving old projects.",
      time: "1h ago",
      icon: "triangle-exclamation",
      tone: "warning")
  ], direction: "column", gap: "s")
])

PersonChip + Quote + Comment

_app_ = Stack([
  PersonChip("Ada Lovelace", role: "Engineering Lead", status: "online"),
  Quote("Aktion lets us ship rich assistant responses without juggling templates.",
    attribution: "— Engineering Lead, Globex",
    tone: "primary"),
  Comment("Linus Torvalds",
    text: "Looks good — can we ship this on Friday?",
    time: "2h ago")
], direction: "column", gap: "m")

SearchBar + ProgressRing + ChatBubble thread

$q = ""

bar = SearchBar("q", placeholder: "Ask anything…", value: $q)
ring = ProgressRing(72, label: "Indexed",
  description: "Re-running search index",
  tone: "primary")
thread = Stack([
  ChatBubble("You", text: "Summarise yesterday's standup.",
    time: "9:01", role: "user"),
  ChatBubble("Assistant", text: "Three updates: design review shipped, auth migration moved to next sprint, mobile build green.",
    time: "9:01", role: "assistant"),
  ChatBubble("You", text: "Send that to Slack #standup.",
    time: "9:02", role: "user")
], direction: "column", gap: "s")
_app_ = Stack([bar, ring, thread], direction: "column", gap: "l")

Menus, overlays & toasts

Dropdowns, popovers and toasts are first-class. Below the dropdown exposes a sections-based menu; the popover holds a small filter panel; and the toast stack composes Toast(...) children directly inside a Stack.

Toast stack (bottom-right)

_app_ = Stack([
  Toast("Deployment finished",
    description: "Production is live · v2.4.0",
    tone: "success",
    icon: "rocket"),
  Toast("Backup completed",
    description: "Latest snapshot stored in s3://backups",
    tone: "primary",
    icon: "cloud-arrow-up"),
  Toast("Connection unstable",
    description: "Retrying in 12 s…",
    tone: "warning",
    icon: "triangle-exclamation")
], direction: "column", gap: "s", position: "bottom-right")

Extended form inputs

Sliders, number inputs, date pickers, comboboxes and file uploads — all bound through the same $ reactive atoms as the rest of the library.

Slider + NumberInput + DatePicker

$budget = 1000
$seats = 5
$startsOn = "2026-06-01"

_app_ = Card([
  CardHeader("Trip planner"),
  FormControl("Budget (USD)", Slider("budget",
    min: 0, max: 5000, step: 50, value: $budget, showValue: true)),
  FormControl("Seats", NumberInput("seats",
    value: $seats, min: 1, max: 12)),
  FormControl("Departs on", DatePicker("startsOn",
    value: $startsOn,
    min: "2026-01-01",
    max: "2027-01-01"))
])

Combobox + FileUpload

$city = ""

_app_ = Card([
  CardHeader("Onboarding"),
  FormControl("City", Combobox("city", [
    SelectItem("ams", label: "Amsterdam"),
    SelectItem("ber", label: "Berlin"),
    SelectItem("lis", label: "Lisbon"),
    SelectItem("ny",  label: "New York"),
    SelectItem("sf",  label: "San Francisco")
  ], value: $city, placeholder: "Search a city…")),
  FormControl("Avatar", FileUpload("avatar",
    label: "Drop an image",
    hint: "PNG or JPG up to 2 MB",
    accept: "image/*"))
])

Hierarchical data & top navigation

File browser tree

_app_ = Card([
  CardHeader("Workspace files"),
  TreeNode("docs", icon: "folder", expanded: true, children: [
    TreeNode("guides", icon: "folder", children: [
      TreeNode("get-started.md", icon: "file"),
      TreeNode("language.md",    icon: "file")
    ]),
    TreeNode("api.md", icon: "file", selected: true)
  ])
])

Navbar with brand, links & actions

action goPricing() { _route_.navigate("/pricing") }

_app_ = Navbar(
  brand: "Streaming",
  items: [
    NavbarItem("Product",  to: "/product"),
    NavbarItem("Pricing",  to: "/pricing"),
    NavbarItem("Docs",     to: "/docs"),
    NavbarItem("Blog",     to: "/blog")
  ],
  actions: [
    Button("Sign in", variant: "ghost"),
    Button("Start free trial", action: goPricing, variant: "primary")
  ],
  sticky: true)

Real-world blocks

Register / sign-up

$email = ""
$password = ""
$accept = false

action register() {
  emit "assistant-message" { message: `Register ${$email}` }
}

_app_ = Card([
  CardHeader("Create your account", subtitle: "Free for 14 days"),
  FormControl("Email", Input("email",
    type: "email", placeholder: "you@example.com", value: $email)),
  FormControl("Password", PasswordInput("password",
    value: $password, placeholder: "At least 8 characters", showStrength: true)),
  Checkbox("accept",
    label: "I agree to the terms of service",
    checked: $accept),
  Button("Create account", action: register, variant: "primary",
    disabled: $email == "" || $password == "" || !$accept)
])

Product listing

_app_ = AppShell(content: [pages])

  # --- Logic & State ---
  $category = "all"
  $cart = []
  
  action addToCart(product) {
    $cart = [...$cart, product]
    Toast("Added to cart", message: `${product.name} is now in your bag.`, tone: "success", icon: "cart-plus")
  }
  
  action checkout() {
    if $cart.length == 0 { return }
    $checkout = http({ url: "/api/checkout", method: "POST", body: { items: $cart } })
    $cart = []
    _route_.navigate("/success")
  }
  
  # --- Components ---
  component CatalogPage() {
    $visible = if $category == "all" { products } else { @Filter(products, "category", "==", $category) }
    
    header = PageHeader("Product Catalog", 
      subtitle: "Explore our latest arrivals and essentials",
      actions: [
        Popover(
          trigger: Button(`Cart (${$cart.length})`, icon: "shopping-bag", variant: "secondary"),
          title: "Your Shopping Bag",
          content: [cartContent]
        )
      ]
    )
  
    filters = Toolbar(
      left: [
        Select("cat-select", [
          SelectItem("all", "All Categories"),
          SelectItem("apparel", "Apparel"),
          SelectItem("home", "Home Decor"),
          SelectItem("tech", "Technology")
        ], value: $category, label: "Filter by")
      ],
      right: [Text(`${$visible.length} products found`, tone: "muted")]
    )
  
    cards = Grid(for p in $visible {
      MediaCard(p.name,
        imageSrc: p.img,
        description: @Format(p.price, "currency", "USD"),
        badge: Badge(p.category, tone: "info"),
        actions: [
          Button("Add to cart", 
            icon: "plus", 
            onClick: () => addToCart(p), 
            variant: "primary", 
            fullWidth: true
          )
        ],
        ratio: "4:3"
      )
    }, columns: { sm: 1, md: 2, lg: 4 }, gap: "m")
  
    return Stack([header, filters, cards], gap: "xl")
  }
  
  cartContent = Stack([
    if $cart.length == 0 {
      EmptyState("Your cart is empty", icon: "cart-shopping")
    } else {
      Stack([
        for item in $cart {
          PersonChip(item.name, role: @Format(item.price, "currency", "USD"), avatarSrc: item.img)
        },
        Separator(),
        Stack([
          Text("Total", variant: "body-heavy"),
          Spacer(),
          Text(@Format(@Sum($cart.price), "currency", "USD"), variant: "large-heavy")
        ], direction: "row"),
        Button("Checkout", onClick: checkout, variant: "primary", fullWidth: true)
      ], gap: "s")
    }
  ], padding: "m", maxWidth: "320px")
  
  # --- Router & Layout ---
  pages = _router_({
    "/": CatalogPage(),
    "/success": SuccessState("Order Placed!", 
      description: "Thank you for your purchase. Your items will ship soon.",
      actions: [Button("Back to Shop", onClick: () => _route_.navigate("/"))]
    ),
    default: CatalogPage()
  })
  
  # --- Data ---
  products = [
    { id: "p1", name: "Aurora Hoodie",  price: 78,  category: "apparel", img: "https://images.unsplash.com/photo-1556905055-8f358a7a47b2?auto=format&w=400" },
    { id: "p2", name: "Voyager Mug",    price: 24,  category: "home",    img: "https://images.unsplash.com/photo-1485955900006-10f4d324d411?auto=format&w=400" },
    { id: "p3", name: "Glide Sneakers", price: 142, category: "apparel", img: "https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&w=400" },
    { id: "p4", name: "Halo Lamp",      price: 96,  category: "home",    img: "https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?auto=format&w=400" },
    { id: "p5", name: "Cyber Watch",    price: 299, category: "tech",    img: "https://images.unsplash.com/photo-1523275335684-37898b6baf30?auto=format&w=400" },
    { id: "p6", name: "Studio Pods",    price: 199, category: "tech",    img: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?auto=format&w=400" }
  ]

FAQ

_app_ = Card([
  CardHeader("Frequently asked questions"),
  Accordion([
    AccordionItem("What's included in the free plan?",
      children: Text("Three projects, community support and our standard themes.")),
    AccordionItem("Do you ship internationally?",
      children: Text("Yes — to 47 countries with standard duties handled at checkout.")),
    AccordionItem("Can I cancel anytime?",
      children: Text("Cancellations stop the next billing cycle; you keep access through the period.")),
    AccordionItem("Is there a long-term contract?",
      children: Text("No — month-to-month or yearly plans only."))
  ])
])

Shopping cart

$cart = [
  { id: "p1", name: "Aurora Hoodie",  price: 78,  qty: 1 },
  { id: "p2", name: "Voyager Mug",    price: 24,  qty: 2 }
]

action checkout() {
  emit "assistant-message" { message: "Proceed to checkout" }
}

$lines = for item in $cart { item.qty * item.price }
$subtotal = @Sum($lines)
$shipping = if $subtotal >= 100 { 0 } else { 9 }
$total = $subtotal + $shipping

items = for item in $cart {
  ListItem(item.name,
    meta: `${item.qty} × ${@Format(item.price, "currency", "USD")}`,
    icon: "box")
}

summary = Card([
  SectionHeader("Order summary"),
  DescriptionList([
    DescriptionItem("Subtotal", value: @Format($subtotal, "currency", "USD")),
    DescriptionItem("Shipping", value: if $shipping == 0 { "Free" } else { @Format($shipping, "currency", "USD") }),
    DescriptionItem("Total",    value: @Format($total, "currency", "USD"), icon: "sack-dollar")
  ]),
  Button("Checkout", action: checkout, variant: "primary")
])

_app_ = Stack([
  Card([CardHeader("Your cart"), List(items)]),
  summary
], direction: "column", gap: "m")

JavaScript-powered recipes

Every program below drops into a js{ ... } block inside an effect or action body. Every runtime primitive is always available — see the JavaScript interactions guide for the full ctx surface.

Live ticking counter (effect)

$ticks = 0

effect [on:every(1000)] {
  js{ ctx.state.set("ticks", (ctx.state.get("ticks") ?? 0) + 1) }
}

action reset() { $ticks = 0 }

_app_ = Card([
  CardHeader("Live counter", subtitle: "Increments once per second"),
  Stack([
    Text(`Ticks: ${$ticks}`, variant: "large-heavy"),
    Button("Reset", action: reset, variant: "secondary", size: "small")
  ], direction: "row", gap: "m", align: "center", justify: "between")
])

Copy to clipboard (action + js{})

$copied = false
snippet = "npm install aktion"

action copy() {
  js{ await navigator.clipboard.writeText(ctx.args.snippet);
      ctx.state.set("copied", true) }
}

effect [$copied] {
  js{ if (!ctx.state.get("copied")) return;
      const id = setTimeout(() => ctx.state.set("copied", false), 1800);
      ctx.cleanup(() => clearTimeout(id)) }
}

copyBtn = Button(if $copied { "Copied" } else { "Copy" },
  action: () => copy({ snippet: snippet }),
  variant: if $copied { "secondary" } else { "primary" },
  size: "small")

_app_ = Card([
  CardHeader("Install", subtitle: "One command via npm"),
  Stack([
    Text(snippet, variant: "body-heavy"),
    copyBtn
  ], direction: "row", gap: "m", align: "center", justify: "between")
])

Live form validation (effect + reactive errors)

$email = ""
$password = ""
$emailError = ""
$pwError = ""
$strength = ""

effect [$email, $password] {
  js{
    const email = (ctx.state.get("email") ?? "").trim();
    const pw = ctx.state.get("password") ?? "";
    const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    ctx.state.set("emailError",
      !email ? "" : (emailRe.test(email) ? "" : "Looks like an invalid email."));
    let strength = "";
    if (pw.length === 0) strength = "";
    else if (pw.length < 8) strength = "Too short — use at least 8 characters.";
    else if (!/\d/.test(pw) || !/[A-Z]/.test(pw)) strength = "Add a number and an uppercase letter.";
    else strength = "Strong enough.";
    ctx.state.set("strength", strength);
    ctx.state.set("pwError", pw && pw.length < 8 ? "Password is too short." : "");
  }
}

action createAccount() {
  emit "assistant-message" { message: "Account created!" }
}

$canSubmit = $email != "" && $password != "" && $emailError == "" && $pwError == ""

_app_ = Stack([
  Card([
    CardHeader("Sign up", subtitle: "Validation runs in-browser as you type"),
    FormControl("Email", Input("email",
      type: "email", placeholder: "you@example.com", value: $email)),
    if $emailError != "" { Callout("error", title: "Email", text: $emailError) } else { null },
    FormControl("Password", Input("password",
      type: "password", placeholder: "At least 8 characters", value: $password)),
    if $strength != "" {
      Callout(if $pwError == "" { "info" } else { "warning" },
        title: "Password strength", text: $strength)
    } else { null },
    Button("Create account",
      action: createAccount,
      variant: if $canSubmit { "primary" } else { "ghost" })
  ])
])