| name | folio-slim |
| description | Slim template formatting and best practices for Folio components and views. Use when writing or editing .slim templates, reviewing Slim formatting, or when the user asks about Slim conventions, multi-line attributes, or template structure in Folio. |
Slim templates (Folio)
Avoid inline Ruby
Don't put logic in - lines. Extract a private component method and call it with =:
/ Bad
- classes = ["foo", ("bar" if @active)].compact.join(" ")
div class=classes
/ Good — move to a component method
div class=wrapper_class_name
When a plain view still needs several Ruby setup lines, prefer one ruby:
block over repeated - lines. In ViewComponent templates, move that setup to
private component methods whenever practical; in Rails views, a short ruby:
block is acceptable and clearer than multi-line - control flow.
Multi-line attributes
When an element has multiple attributes, use bracket syntax without a space before [:
a[
class="f-c-ui-button"
class="f-c-ui-button--primary"
href=@url
data=stimulus_action(click: "onClick")
]
For a single short attribute, keep it inline:
a href="#section"
span.badge data-count=@count
If an attribute value becomes complex or multi-line, move it to a private
component method so the template stays concise:
/ Bad
button data=stimulus_action({ click: "accept", keydown: "acceptFromKeyboard" },
{ text: suggestion.text, key: suggestion.key })
/ Good
button data=suggestion_data(suggestion)
Do not split a single Slim attribute value expression across lines. If an
attribute value is too long or complex, extract it to a component method instead
of wrapping the Ruby call inside the attribute.
Multiple class attributes
Use separate class attributes instead of string concatenation or array joining:
/ Good
div[
class="f-c-ui-alert"
class="f-c-ui-alert--danger"
class=@class_name
]
/ Bad
div class="f-c-ui-alert f-c-ui-alert--danger #{@class_name}"
Slim merges multiple class attributes automatically. nil values are safely ignored.
Shorthand classes
Use .class-name shorthand on div (tag is implicit) and tag.class-name on other elements:
.f-c-ui-modal__body
span.f-c-ui-modal__label = @label
button.f-c-ui-modal__close type="button"
When an element already uses bracket syntax with multiple class attributes, don't mix .shorthand — use class attributes consistently:
/ Good — all classes as attributes
li[
class="nav-item"
class="f-c-ui-tabs__nav-item"
hidden=tab[:hidden]
]
/ Bad — mixing shorthand with class attributes
li.nav-item[
class="f-c-ui-tabs__nav-item"
hidden=tab[:hidden]
]
Boolean attributes
Pass the value directly — Slim omits the attribute when nil or false:
input disabled=@disabled
button hidden=@hidden
Output
= for escaped Ruby output (default, safe)
' at line start for a trailing space (e.g. between inline elements)
- Avoid
== (unescaped output). Prefer = safe_join(...) or = tag.span(...) to build HTML safely:
/ Bad — unescaped, risky
== [t(".line_one"), t(".line_two")].join("<br>")
/ Good — safe_join handles html_safe pieces
= safe_join([t(".line_one"), t(".line_two")], tag.br)
Use == only when rendering a trusted html_safe string where no safe alternative exists.
I18n keys
Prefer relative I18n keys starting with . in views and component templates.
Do not flag relative keys as issues; they are intentional and preferred.
Keep templates short
If a template exceeds ~50 lines or has deeply nested conditionals, split into child ViewComponents or slots. The template should be mostly structure; logic belongs in component methods.
See folio-view-component skill for composition patterns.
Comments
Use Slim comments (/) which are stripped from output. Avoid HTML comments (/!) unless they must appear in the rendered page.
Dynamic tag splat
Use *method to build a tag from a hash (tag name, attributes) returned by a component method:
*tag
= @label
Where tag is a component method returning e.g. { tag: :a, href: @url, class: "btn" }.
Quality gates
slim-lint <path> on all Slim files (see AGENTS.md).
Reference