Skip to content

Linter Rule: Disallow conditional open tags

Rule: erb-no-conditional-open-tag

Description

Disallow the pattern of using ERB conditionals to vary only the open tag of an element (typically to conditionally change attributes) while sharing the same tag name, body content, and close tag.

Rationale

This pattern can be difficult to read and maintain. The conditional logic is split from the element's body and closing tag, making it harder to understand the full structure at a glance.

Prefer using:

  • content_tag with conditional attributes
  • A ternary or conditional for the class/attribute value directly
  • Separate complete elements in each branch if the differences are significant

Examples

✅ Good

Using conditional attributes directly:

erb
<div class="<%= @condition ? 'a' : 'b' %>">
  Content
</div>

Using content_tag with conditional options:

erb
<%= content_tag :div, class: (@condition ? 'a' : 'b') do %>
  Content
<% end %>

Using class_names helper:

erb
<div class="<%= class_names('base', 'a': @condition, 'b': !@condition) %>">
  Content
</div>

Complete separate elements when differences are significant:

erb
<% if @condition %>
  <div class="a" data-foo="bar">Content</div>
<% else %>
  <div class="b" data-baz="qux">Content</div>
<% end %>

Self-closing/void elements in conditionals (each branch is complete):

erb
<% if @large %>
  <img src="photo.jpg" width="800" height="600">
Missing required `alt` attribute on `<img>` tag. Add `alt=""` for decorative images or `alt="description"` for informative images. (html-img-require-alt)
<% else %> <img src="photo.jpg" width="400" height="300">
Missing required `alt` attribute on `<img>` tag. Add `alt=""` for decorative images or `alt="description"` for informative images. (html-img-require-alt)
<% end %>

🚫 Bad

Conditional open tags with shared body and close tag:

erb
<% if @condition %>
Avoid using ERB conditionals to split the open and closing tag of `<div>` element. (erb-no-conditional-open-tag)
<div class="a"> <% else %> <div class="b"> <% end %> Content </div>
erb
<% if @with_icon %>
Avoid using ERB conditionals to split the open and closing tag of `<button>` element. (erb-no-conditional-open-tag)
<button class="btn btn-icon" aria-label="Action"> <% else %> <button class="btn"> <% end %> Click me </button>

Open tag in conditional without else branch:

erb
<% if @wrap %>
  <div class="wrapper">
Opening tag `<div>` at (2:3) doesn't have a matching closing tag `</div>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`) (parser-no-errors)
<% end %> Content </div>
Found closing tag `</div>` at (5:2) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`) (parser-no-errors)

Missing open tag in else branch:

erb
<% if @style == "a" %>
  <div class="a">
Opening tag `<div>` at (2:3) doesn't have a matching closing tag `</div>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`) (parser-no-errors)
<% elsif @style == "b" %> <div class="b">
Opening tag `<div>` at (4:3) doesn't have a matching closing tag `</div>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`) (parser-no-errors)
<% else %> <% end %> Content </div>
Found closing tag `</div>` at (8:2) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`) (parser-no-errors)

Missing open tag in elsif branch:

erb
<% if @style == "a" %>
  <div class="a">
Opening tag `<div>` at (2:3) doesn't have a matching closing tag `</div>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`) (parser-no-errors)
<% elsif @style == "b" %> <%# no-open-tag %> <% else %> <div class="c">
Opening tag `<div>` at (6:3) doesn't have a matching closing tag `</div>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`) (parser-no-errors)
<% end %> Content </div>
Found closing tag `</div>` at (9:2) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`) (parser-no-errors)

References

-

Released under the MIT License.