RRUI-007 · Alert · Icon alignment

Alert icon is not vertically aligned with the text

With the default type icon on, the glyph sits higher than the first line of copy. Body text has a top margin that the icon never gets, so the two columns don’t share a first-line baseline. Turning the icon off (or dropping the extra margin) makes the text sit flush with the padding; the mismatch only shows when the icon is present.

Open gem 0.58.1 2026-08-17

MESSAGE_CLASSES = "mt-1" is always applied — including when there is no title. That spacing is title o-body, not first-line padding. The icon wrapper has no matching offset and no first-line align, so the icon row starts at the flex top while the copy starts 4px lower.

Summary

Component RapidRailsUI::Alert::Component
Trigger show_icon: true (default) + body copy (block or messages:), with or without title:
Symptom Icon sits above the first line of text; optical top edges don’t match
Worse when No title — mt-1 still fires on the only text node

Repro

<%= rui_alert(:success) do %>
  Someone from the team will get back to you.
<% end %>

<%= rui_alert(:info, title: "Note") do %>
  Here's some helpful information.
<% end %>
  1. Actual: Check / info glyph top is above the first line of text (body-only is the obvious case: 4px mt-1 on the copy, none on the icon).
  2. Set show_icon: false — extra indent relative to an icon disappears; remaining mt-1 is just empty padding at the top of the text column.
  3. Expected: Icon optical box aligns with the first line (title if present, otherwise body). Title o-body spacing only when both exist.

Evidence (0.58.1)

1. Body always gets title spacing

MESSAGE_CLASSES = "mt-1"
def render_messages
  if @messages.present?
    render_message_list
  elsif content.present?
    content_tag(:div, content, class: Styles::MESSAGE_CLASSES)
  end
end

Single-string messages: uses the same class. There is no title-present? gate.

2. Icon column has no matching offset

ICON_WRAPPER_CLASSES = "flex-shrink-0"

content_tag(:div, class: "flex gap-3 w-full") do
  safe_join([render_icon, render_content, render_dismiss_area].compact)
end

Inner row is default align-items: stretch. Icon wrapper is not self-start / items-start. SVG is inline (Lucide), so it sits on a line box while the text column starts after mt-1.

3. Dismiss already nudges; icon does not

DISMISS_CLASSES = "flex-shrink-0 -mr-1 -mt-1"

The close control is optically pulled up. The leading icon has no equivalent first-line compensation.

4. Size pairing

Default :base alert is text-base with icon size :lg. Even after dropping stray mt-1, first-line align still needs items-start (and possibly a 1px optical tweak) so a larger glyph matches the title/body cap-height.

Fix in the gem

  1. Apply body top margin only when a title is present:
def render_messages
  spacing = @title.present? ? Styles::MESSAGE_CLASSES : nil
  content_tag(:div, content, class: spacing) if content.present?
end
  1. Inner row: flex items-start gap-3 w-full.
  2. Icon wrapper: flex-shrink-0 self-start; make the SVG block so it isn’t baseline-aligned. Optional: small pt to match cap-height of text-* at each size.
  3. Single-line, no-title alerts can use items-center instead of items-start if that reads better at xs/sm.
  4. Regression: body-only + icon — assert no mt-1 on the message node; title+body — keep spacing; icon box top aligns with first text line in screenshot/Lookbook.

Done when