ghtml
Write HTML templates. Get type-safe Gleam. Like magic. β¨
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.
π Watch Mode
Change a file. Blink. Itβs regenerated. Your flow stays unbroken.
π― 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.
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
π¦ Imports & Parameters
@import(gleam/int)
@import(app/models.{type User})
@params(
user: User,
count: Int,
on_click: fn() -> msg,
)
β¨ Interpolation
<!-- Expressions -->
<p>{user.name}</p>
<p>{int.to_string(count)} items</p>
<!-- Literal braces -->
<p>Use {{ and }} for literal braces</p>
π Control Flow
<!-- Conditionals -->
{#if show}
<p>Visible!</p>
{:else}
<p>Hidden</p>
{/if}
<!-- Loops -->
{#each items as item, index}
<li>{int.to_string(index)}: {item}</li>
{/each}
<!-- Pattern matching -->
{#case status}
{:Active}
<span class="green">Active</span>
{:Pending}
<span class="yellow">Pending</span>
{/case}
π― Attributes & Events
<!-- Static attributes -->
<div class="container" id="main">
<!-- Dynamic attributes -->
<input value={model.text} placeholder={hint} />
<!-- Boolean attributes -->
<input disabled required />
<!-- Events -->
<button @click={on_submit}>Submit</button>
<input @input={handle_change} @blur={on_blur} />
Example
Input: src/components/user_card.ghtml
@import(gleam/int)
@params(name: String, count: Int)
<div class="card">
<h1>{name}</h1>
<p>{int.to_string(count)} items</p>
</div>
Output: src/components/user_card.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 β API reference and guides
- π€ Contributing β Development setup and guidelines
- π Examples β Working example projects
Made for Lustre π
This tool is built specifically for the Lustre ecosystem. If youβre building web apps with Gleam, youβre in the right place.
Built with β and too many brackets by @burakcorekci