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@12. Create a template
Create src/components/greeting.ghtml:
@params(name: String)
<div class="greeting">
<h1>Hello, {name}!</h1>
</div>3. Generate
gleam run -m ghtml4. 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
```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}๐ฏ Attributes & Events
```html{name}
{int.to_string(count)} items
Built with โ and too many brackets by @burakcorekci