ghtml logo

ghtml

Write HTML templates. Get type-safe Gleam. Like magic. โœจ

testPackage VersionHex DocsLicense

ghtml demo


The Problem

Ever found yourself writing Lustre views like this? ๐Ÿ˜ฉ

html.div([attribute.class("card")], [
  html.div([attribute.class("card-header")], [
    html.h1([attribute.class("title")], [text(user.name)]),
    html.span([attribute.class("badge")], [text("Admin")]),
  ]),
  html.div([attribute.class("card-body")], [
    html.p([], [text(description)]),
    // wait, did I close all the brackets...?
  ]),  // <-- is this right?
])     // <-- or this one?

Bracket-counting nightmares. We've all been there. ๐Ÿคฏ

The Solution

Write this instead:

@params(user: User, description: String)

<div class="card">
  <div class="card-header">
    <h1 class="title">{user.name}</h1>
    <span class="badge">Admin</span>
  </div>
  <div class="card-body">
    <p>{description}</p>
  </div>
</div>

Run gleam run -m ghtml and boom โ€” you get a perfectly formatted, type-safe Gleam module. ๐ŸŽ‰


Quick Start

1. Install

gleam add ghtml@1

2. Create a template

Create src/components/greeting.ghtml:

@params(name: String)

<div class="greeting">
  <h1>Hello, {name}!</h1>
</div>

3. Generate

gleam run -m ghtml

4. Use it

import components/greeting

pub fn view(model: Model) -> Element(Msg) {
  greeting.render(model.name)
}

That's it. You're done. Go grab a coffee. โ˜•


Features

โšก Blazing Fast

Hash-based caching means we only rebuild what changed. Run it a thousand times โ€” if nothing changed, nothing rebuilds.

Hash-based caching demo

๐Ÿ‘€ Watch Mode

Change a file. Blink. It's regenerated. Your flow stays unbroken.

Watch mode demo

๐ŸŽฏ Control Flow

{#if}, {#each}, {#case} โ€” all the control flow you need, right in your templates.

{#if user.is_admin}
  <span class="badge">Admin</span>
{/if}

{#each items as item}
  <li>{item}</li>
{/each}

๐Ÿงน Auto Cleanup

Delete a .ghtml file and we clean up the generated .gleam file automatically. No orphans left behind.

Auto cleanup demo

Outside of watch mode, you can manually remove orphaned files:

gleam run -m ghtml -- clean

๐ŸŽจ Events

Event handlers? We got 'em.

<button @click={on_save}>Save</button>
<input @input={handle_input} />

๐Ÿ”ง Custom Elements

Web components work too. Tags with hyphens automatically use element().

<my-component data={value}>
  <slot-content />
</my-component>

Template Syntax

Control flow syntax demo

๐Ÿ“ฆ Imports & Parameters ```html @import(gleam/int) @import(app/models.{type User}) @params( user: User, count: Int, on_click: fn() -> msg, ) ```
โœจ Interpolation ```html

{user.name}

{int.to_string(count)} items

Use {{ and }} for literal braces

```
๐Ÿ”€ Control Flow ```html {#if show}

Visible!

{:else}

Hidden

{/if} {#each items as item, index}
  • {int.to_string(index)}: {item}
  • {/each} {#case status} {:Active} Active {:Pending} Pending {/case} ```
    ๐ŸŽฏ Attributes & Events ```html
    Submit ```
    --- ## Example **Input:** `src/components/user_card.ghtml` ```html @import(gleam/int) @params(name: String, count: Int)

    {name}

    {int.to_string(count)} items

    ``` **Output:** `src/components/user_card.gleam` ```gleam // @generated from user_card.ghtml // @hash abc123... // DO NOT EDIT - regenerate with: gleam run -m ghtml import gleam/int import lustre/attribute import lustre/element.{type Element, text} import lustre/element/html pub fn render(name: String, count: Int) -> Element(msg) { html.div([attribute.class("card")], [ html.h1([], [text(name)]), html.p([], [text(int.to_string(count) <> " items")]), ]) } ``` --- ## Commands | Command | What it does | |---------|--------------| | `gleam run -m ghtml` | Generate all (skips unchanged) | | `gleam run -m ghtml -- force` | Force regenerate everything | | `gleam run -m ghtml -- watch` | Watch mode | | `gleam run -m ghtml -- clean` | Remove orphans only | --- ## Documentation - ๐Ÿ“– [**Full Documentation**](https://hexdocs.pm/ghtml/) โ€” API reference and guides - ๐Ÿค [**Contributing**](CONTRIBUTING.md) โ€” Development setup and guidelines - ๐Ÿ“ [**Examples**](examples/) โ€” Working example projects --- ## Made for Lustre ๐Ÿ’– This tool is built specifically for the [Lustre](https://github.com/lustre-labs/lustre) ecosystem. If you're building web apps with Gleam, you're in the right place. ---

    Built with โ˜• and too many brackets by @burakcorekci