Skip to content

dr34ming/slime_heex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SlimeHEEx

Slim-style templates for Phoenix LiveView.

Write this:

div.container
  h1.title = @page_title
  = for item <- @items do
    .card phx-click="select" phx-value-id={item.id}
      span.name = item.name

Get this:

<div class="container">
  <h1 class="title"><%= @page_title %></h1>
  <%= for item <- @items do %>
    <.card phx-click="select" phx-value-id={item.id}>
      <span class="name"><%= item.name %></span>
    </.card>
  <% end %>
</div>

Why

Two reasons.

Closing tags are unnecessary noise. Indentation already communicates structure. Every </div> is a line that exists only for the parser, not for you. Slim figured this out in 2010. HEEx templates are verbose — a typical LiveView template is 40-60% closing tags and boilerplate. Slime cuts that in half.

Agents read templates too. Every AI coding agent — Claude Code, Cursor, Copilot — parses your templates into its context window. Tokens spent on </div> are tokens not spent on understanding your logic. In a world where AI agents write and modify templates constantly, concise source format isn't just preference — it's throughput. Elixir is already one of the best languages for agent-assisted development (pattern matching, pipelines, explicit data flow). SlimeHEEx extends that advantage to the template layer.

How it works

SlimeHEEx is a preprocessor. Write .slime files, run mix slime, get .heex files. Phoenix picks them up normally. You never fight the framework — you just have a nicer authoring layer.

mix slime                     # Convert all .slime files under lib/
mix slime path/to/file.slime  # Convert a specific file

Syntax

Tags

div                       / <div></div>
p Hello world             / <p>Hello world</p>
h1.title Page Title       / <h1 class="title">Page Title</h1>

Classes and IDs

div.foo.bar               / <div class="foo bar"></div>
div#main                  / <div id="main"></div>
div#main.foo.bar          / <div id="main" class="foo bar"></div>
.container.mx-auto        / implicit div: <div class="container mx-auto"></div>

Attributes

a href="/home" Home                      / string attributes
div class={@class}                       / elixir expression attributes
button phx-click="save" Save             / phoenix event attributes
div :if={@show}                          / conditional rendering
input type="text" name="q"               / void elements self-close

Nesting

Indentation defines structure. Two spaces per level.

div.outer
  div.inner
    p Deeply nested

Elixir expressions

= outputs the expression. - executes without output.

= @user.name                             / <%= @user.name %>
= for item <- @items do                  / block expressions auto-close
  div = item.name

= if @show do
  div.visible Content
- else
  div.hidden Nothing

Inline expressions

Trailing = expr on a tag outputs the expression inside the tag.

span.name = user.name                    / <span class="name"><%= user.name %></span>

Components

.component_name (with underscores) renders as a function component.

.icon name="hero-star"                   / <.icon name="hero-star" />
.modal title="Settings"                  / <.modal title="Settings">
  p Content                              /   <p>Content</p>
                                         / </.modal>

Slots

.table rows={@users}
  :col :let={user} label="Name"
    span = user.name

Comments

/ This comment is stripped from output
/! This becomes an HTML comment          / <%!-- This becomes an HTML comment --%>

Raw text

| This text passes through as-is

Doctype

doctype html                             / <!DOCTYPE html>

Limitations

  • Tailwind fractional/responsive classes like py-1.5, sm:grid-cols-4, border-emerald-700/50 conflict with dot notation. Use the class="..." attribute form for these. Simple classes like .font-bold.text-sm work fine with dots.
  • No multiline attribute support yet.
  • No file watcher — manual mix slime for now.
  • No source maps — errors point at generated .heex lines.

Installation

Add to your mix.exs:

def deps do
  [
    {:slime_heex, "~> 0.1.0"}
  ]
end

Or just copy lib/slime_heex/ into your project. It's three files.

Status

Spike. 28 tests passing. Used in production at Orca. Contributions welcome.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages