Back to catalog

html-guidelines

Provides HEEx template and Phoenix HTML best practices. Use when writing Phoenix templates, forms, handling conditionals in HEEx, working with class attributes, or dealing with interpolation syntax.

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • GitHub stars: 1
  • Updated: 2025-11-21

Download the latest version (v1.0.0) from this page.

SKILL.md

---
name: html-guidelines
description: Provides HEEx template and Phoenix HTML best practices. Use when writing Phoenix templates, forms, handling conditionals in HEEx, working with class attributes, or dealing with interpolation syntax.
---

# Phoenix HTML & HEEx Guidelines

This skill provides best practices for writing HEEx templates and Phoenix HTML code.

## Template Format

- Phoenix templates **always** use `~H` sigil or `.html.heex` files (HEEx)
- **Never** use the deprecated `~E` sigil

## Forms

### Modern Form Helpers

- **Always** use the imported `Phoenix.Component.form/1` and `Phoenix.Component.inputs_for/1`
- **Never** use `Phoenix.HTML.form_for` or `Phoenix.HTML.inputs_for` (these are outdated)

### Form Data with to_form/2

**Always** use `Phoenix.Component.to_form/2`:

```elixir
# In your LiveView or controller
assign(socket, form: to_form(changeset))
```

```heex
<!-- In your template -->
<.form for={@form} id="msg-form">
  <.input field={@form[:email]} label="Email" />
  <.input field={@form[:password]} type="password" label="Password" />
</.form>
```

## DOM IDs for Testing

**Always** add unique DOM IDs to key elements (forms, buttons, etc.) for testing:

```heex
<.form for={@form} id="product-form">
  <.button id="submit-button">Submit</.button>
</.form>
```

## App-Wide Imports

For "app wide" template imports, add them to `my_app_web.ex`'s `html_helpers` block. They'll be available to all LiveViews, LiveComponents, and modules using `use MyAppWeb, :html`.

## Conditionals

### No else if Support

Elixir supports `if/else` but **does NOT support `if/else if` or `if/elsif`**.

**Never do this (invalid)**:

```heex
<%!-- INVALID --%>
<%= if condition do %>
  ...
<% else if other_condition %>
  ...
<% end %>
```

**Always use cond or case instead**:

```heex
<%= cond do %>
  <% condition -> %>
    <p>First condition met</p>
  <% condition2 -> %>
    <p>Second condition met</p>
  <% true -> %>
    <p>Default case</p>
<% end %>
```

## Literal Curly Braces

HEEx requires special tag annotation for literal `{` or `}` characters.

For code snippets in `<pre>` or `<code>`, use `phx-no-curly-interpolation`:

```heex
<code phx-no-curly-interpolation>
  let obj = {key: "val"}
</code>
```

Within annotated tags, dynamic Elixir expressions still work with `<%= ... %>` syntax.

## Class Attributes

HEEx class attributes support lists—**always** use list `[...]` syntax for multiple classes.

### Conditional Classes

Use class list syntax to conditionally add classes:

```heex
<a class={[
  "px-2 text-white",
  @some_flag && "py-5",
  if(@other_condition, do: "border-red-500", else: "border-blue-100")
]}>Text</a>
```

**Important**: Always wrap `if` expressions inside `{...}` with parens.

### Invalid Syntax

**Never** omit the `[` and `]` brackets:

```heex
<%!-- INVALID - missing brackets --%>
<a class={
  "px-2 text-white",
  @some_flag && "py-5"
}>...</a>
<%!-- This raises a compile syntax error --%>
```

## Collections

- **Never** use `<% Enum.each %>` for generating template content
- **Always** use `<%= for item <- @collection do %>`

```heex
<%= for user <- @users do %>
  <div>{user.name}</div>
<% end %>
```

## Comments

HEEx HTML comments use `<%!-- comment --%>`.

```heex
<%!-- This is a HEEx comment --%>
<!-- This is a regular HTML comment -->
```

**Always** use HEEx comment syntax for template comments.

## Interpolation

HEEx allows interpolation via `{...}` and `<%= ... %>`, but `<%= %>` **only** works within tag bodies.

### Rules

- **Always** use `{...}` for interpolation within tag attributes
- **Always** use `{...}` for interpolation of values within tag bodies
- **Always** use `<%= ... %>` for block constructs (`if`, `cond`, `case`, `for`) within tag bodies

### Valid Interpolation

```heex
<div id={@id}>
  {@my_assign}
  <%= if @some_block_condition do %>
    {@another_assign}
  <% end %>
</div>
```

### Invalid Interpolation

**Never do this** (will cause syntax error):

```heex
<%!-- INVALID - DO NOT DO THIS --%>
<div id="<%= @invalid_interpolation %>">
  {if @invalid_block_construct do}
  {end}
</div>
```

## Page Header Pattern

All pages should use the consistent page header pattern with DaisyUI semantic classes:

```heex
<!-- Page Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-8">
  <div>
    <h1 class="text-2xl md:text-3xl font-bold tracking-tight text-base-content">
      Page Title
    </h1>
    <p class="mt-1 text-sm text-base-content/60">
      Description text
    </p>
  </div>
  <div class="mt-4 sm:mt-0 flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
    <!-- Action buttons -->
  </div>
</div>
```

**Typography:**
| Element | Classes |
|---------|---------|
| Page Title | `text-2xl md:text-3xl font-bold tracking-tight text-base-content` |
| Description | `mt-1 text-sm text-base-content/60` |

## Dark Mode with DaisyUI

**Always** use DaisyUI semantic classes for dark mode compatibility:

| Instead of | Use |
|------------|-----|
| `text-gray-900`, `text-black` | `text-base-content` |
| `text-gray-600` | `text-base-content/60` |
| `text-gray-400` | `text-base-content/40` |
| `bg-white` | `bg-base-100` |
| `bg-gray-100` | `bg-base-200` |
| `border-gray-200` | `border-base-300` |

## Summary

- Use `.html.heex` or `~H` for templates
- Use `Phoenix.Component.form/1` and `to_form/2` for forms
- Add unique DOM IDs to elements
- Use `cond` or `case` for multiple conditionals (no `else if`)
- Use `[...]` for class lists with conditional classes
- Use `{...}` in attributes, `<%= ... %>` for block constructs in bodies
- Use `<%!-- --%>` for HEEx comments
- Use consistent page header pattern with responsive layout
- Use DaisyUI semantic classes for dark mode support